diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a40e2e..29411b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main ] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: runs-on: ubuntu-latest @@ -14,10 +17,10 @@ jobs: java: [ '11', '17', '21', '25' ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: ${{ matrix.java }} distribution: temurin @@ -28,7 +31,7 @@ jobs: - name: Upload coverage to Codecov if: matrix.java == '25' - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: files: '**/target/site/jacoco/jacoco.xml' fail_ci_if_error: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5bda5ad..6eae30b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,16 +5,19 @@ on: tags: - 'v*' +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: release: runs-on: ubuntu-latest permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java 11 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '11' distribution: temurin @@ -66,7 +69,7 @@ jobs: ./mvnw clean deploy -P release -B --no-transfer-progress -DskipTests - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: generate_release_notes: true files: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 3719939..97f20fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,29 +7,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.2.1] - 2026-05-06 +## [0.3.0] - 2026-05-28 -### Changed -- **Namespace migration**: - Java package namespace migrated from - `io.github.ivir3zam.*` to - `io.github.decentralizedidentity.*` to reflect the new project - ownership and align with the GitHub organization. -- **Maven coordinates update**: - `groupId` changed from `io.github.ivir3zam` to - `io.github.decentralizedidentity` across all modules to match the - new namespace and publishing coordinates. -- **Repository migration**: - project moved from a personal repository to the - `decentralized-identity` GitHub organization; SCM metadata, - documentation links, and issue tracking URLs updated accordingly. +This release closes the failures reported by the +[did:webvh interop test suite](https://github.com/swcurran/didwebvh-test-suite) +against the v0.2.0 line (see issue #2). + +### Added +- **Negative interop test vectors** vendored from the upstream + did:webvh test suite under + `didwebvh-core/src/test/resources/interop/` + (`basic-create/python`, `basic-update/ts`, full `java-eecc` log + set, `pre-rotation-consume/{rust,java-eecc}`, + `witness-update/rust`, `witness-threshold/rust`, and + `negative-cross-did-witness-replay/ts`), each exercised by a + dedicated JUnit test under `didwebvh-core/.../interop/`. +- **Implicit `#files` and `#whois` services in resolved DID + Documents** (spec §3.8 and §3.9): the resolver now emits a + `relativeRef` `#files` service and a + `LinkedVerifiablePresentation` `#whois` service unless the + controller has already declared services with the same id. The + shared logic lives in `didweb.ImplicitServices` and is also + reused by `DidWebPublisher`. ### Fixed -- **Post-migration build issues**: - resolved inconsistencies caused by outdated package imports and - Maven coordinates after the namespace and repository migration. +- **Cross-DID witness-proof replay** (spec §3.7.5, lines 884-889): + when the `witness` parameter is set to `{}` while witnesses were + active, the transition entry MUST itself be witnessed by the + prior witnesses. `WitnessValidator` was merging the empty config + in first and skipping the entry as inactive, which let an + attacker disable witnessing and replay a stale (or cross-DID) + proof for an earlier entry. The validator now tracks the prior + config and, on a witness-off transition, requires approval from + the prior witnesses. +- **`witness: {}` round-trip across implementations**: Python and + TS serialise an empty `"witness": {}` object in parameters when + no witnesses are configured. Gson was instantiating + `WitnessConfig` via `Unsafe.allocateInstance`, bypassing the + constructor and leaving `witnesses` null — every call to + `isActive()` / `getWitnesses()` then NPE'd on the first + Python/TS log entry. Java was also re-serialising the empty + config as `{"threshold":0,"witnesses":[]}` instead of `{}`, + producing a different JCS canonical form for SCID, entry-hash + and proof computation. Added a no-arg constructor (so Gson uses + `Constructor.newInstance`) and a `WitnessConfigTypeAdapter` that + round-trips the empty-object form. +- **Pre-rotation entries authorized against the wrong key set** + (spec §3.7.5): when the previous entry committed a + `nextKeyHashes`, the active updateKeys for the current entry are + the current entry's own `updateKeys`, not the previous entry's. + `LogChainValidator` was unconditionally using the previous + entry, failing every `pre-rotation-consume` log from rust and + java-eecc. `DeactivateDidOperation` made the same mistake when + emitting its intermediate pre-rotation-consuming entry; it now + signs that entry with `nextRotationSigner`. Regenerated + `pre-rotation-log.jsonl` under the corrected rules. +- **Witness-proof pruning and bare-multikey witness ids** (spec + §3.7.8): a witness proof at versionId V implicitly approves all + prior log entries, and the DID Controller SHOULD prune + `did-witness.json` to retain only the latest proof per witness. + `WitnessValidator` required an exact-versionId proof per entry + and failed with "missing witness proof" against the Rust pruned + files. It also compared witness ids as `did:key:` + against the Rust implementation's bare-multikey form, yielding 0 + authorized proofs. The validator now pre-verifies all proofs + once, counts distinct authorized witnesses per entry, and + matches witness ids by multikey portion to accept both + `did:key:z6Mk…` and bare `z6Mk…` forms. +- **Pre-rotation and portable-SCID negative-test gaps** closed for + `negative-pre-rotation-omit-updatekeys` and + `negative-portable-scid-swap`. +- **Release auth**: auto-detect and decode base64-encoded + `user:pass` `OSSRH_TOKEN` values so Sonatype publishing succeeds + with either token form. + +### Changed +- **CI**: GitHub Actions upgraded to versions compatible with + Node.js 24, and Node.js 24 is forced in both the CI and release + workflows to silence the deprecation warning emitted by older + actions on the GHA runners. -## [0.2.0] - 2026-04-21 +## [0.2.0] - 2026-05-06 ### Added - **Wizard – Export parallel `did:web` document**: new menu option @@ -49,6 +107,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the library can be used standalone without the wizard. ### Changed +- **Project relocation to `decentralized-identity` GitHub + organization**: Java package namespace migrated from + `io.github.ivir3zam.*` to `io.github.decentralizedidentity.*` and + Maven `groupId` changed from `io.github.ivir3zam` to + `io.github.decentralized-identity` across all modules. SCM + metadata, documentation links, and issue tracking URLs updated + accordingly. - **Wizard – update flow preserves existing state**: - Witness configure seeds from the active `WitnessConfig` so existing witnesses are kept and the threshold can span the full merged set. @@ -123,5 +188,7 @@ Initial public release of `didwebvh-java`, a Java 11+ implementation of the `.github/workflows/release.yml` that publishes to Sonatype Central on tag push (`v*`) and attaches JARs to the GitHub Release. -[Unreleased]: https://github.com/decentralized-identity/didwebvh-java/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/decentralized-identity/didwebvh-java/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/decentralized-identity/didwebvh-java/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/decentralized-identity/didwebvh-java/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/decentralized-identity/didwebvh-java/releases/tag/v0.1.0 diff --git a/README.md b/README.md index c56487f..95726c0 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ A Java 11+ library for the [did:webvh](https://didwebvh.info/) (DID Web + Verifi ```xml - io.github.decentralizedidentity + io.github.decentralized-identity didwebvh-java - 0.2.0 + 0.3.0 ``` @@ -44,7 +44,7 @@ need the local-key adapter. ### Gradle ```groovy -implementation 'io.github.decentralized-identity:didwebvh-java:0.2.0' +implementation 'io.github.decentralized-identity:didwebvh-java:0.3.0' ``` ## Library Usage diff --git a/didwebvh-core/pom.xml b/didwebvh-core/pom.xml index e7c557b..b627854 100644 --- a/didwebvh-core/pom.xml +++ b/didwebvh-core/pom.xml @@ -7,7 +7,7 @@ io.github.decentralized-identity didwebvh-java - 0.2.0-SNAPSHOT + 0.3.0-SNAPSHOT didwebvh-core diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/didweb/DidWebPublisher.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/didweb/DidWebPublisher.java index 6caf0a6..d6265e5 100644 --- a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/didweb/DidWebPublisher.java +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/didweb/DidWebPublisher.java @@ -18,12 +18,6 @@ */ public final class DidWebPublisher { - private static final String FILES_ID = "#files"; - private static final String WHOIS_ID = "#whois"; - private static final String WELL_KNOWN_SEGMENT = "/.well-known/"; - private static final String DID_LOG_FILENAME = "did.jsonl"; - private static final String WHOIS_FILENAME = "whois.vp"; - private DidWebPublisher() { } @@ -46,13 +40,12 @@ public static DidDocument toDidWeb(DidDocument resolvedWebVh) { DidWebVhUrl parsed = DidWebVhUrl.parse(didWebVh); String scidPrefix = "did:webvh:" + parsed.getScid() + ":"; - String httpsBase = httpsBase(didWebVh); // Step 1: start from the resolved did:webvh DIDDoc (deep copy to avoid mutation). JsonObject doc = resolvedWebVh.asJsonObject().deepCopy(); // Step 2: add implicit #files and #whois services if not already present. - addImplicitServices(doc, didWebVh, httpsBase); + ImplicitServices.addTo(doc, didWebVh); // Step 3: text-replace did:webvh:: with did:web: across the whole document. String replaced = doc.toString().replace(scidPrefix, "did:web:"); @@ -74,73 +67,6 @@ public static String toDidWebUrl(String didWebVhUrl) { return DidToHttpsTransformer.toDidWebUrl(didWebVhUrl); } - /** - * Compute the HTTPS base URL for implicit service endpoints: the DID-to-HTTPS - * URL with the trailing {@code did.jsonl} filename removed and any - * {@code .well-known/} segment stripped. - */ - private static String httpsBase(String didWebVh) { - String didLogUrl = DidToHttpsTransformer.toHttpsUrl(didWebVh); - // Strip the trailing did.jsonl filename. - String base = didLogUrl.substring(0, - didLogUrl.length() - DID_LOG_FILENAME.length()); - // Omit the ".well-known/" segment if present. - int idx = base.indexOf(WELL_KNOWN_SEGMENT); - if (idx >= 0) { - base = base.substring(0, idx + 1) + base.substring(idx + WELL_KNOWN_SEGMENT.length()); - } - return base; - } - - private static void addImplicitServices(JsonObject doc, String didWebVh, - String httpsBase) { - JsonArray services; - JsonElement existing = doc.get("service"); - if (existing != null && existing.isJsonArray()) { - services = existing.getAsJsonArray(); - } else { - services = new JsonArray(); - doc.add("service", services); - } - - if (!hasServiceWithId(services, didWebVh, FILES_ID)) { - JsonObject files = new JsonObject(); - files.addProperty("id", didWebVh + FILES_ID); - files.addProperty("type", "relativeRef"); - files.addProperty("serviceEndpoint", httpsBase); - services.add(files); - } - - if (!hasServiceWithId(services, didWebVh, WHOIS_ID)) { - JsonObject whois = new JsonObject(); - whois.addProperty("@context", - "https://identity.foundation/linked-vp/contexts/v1"); - whois.addProperty("id", didWebVh + WHOIS_ID); - whois.addProperty("type", "LinkedVerifiablePresentation"); - whois.addProperty("serviceEndpoint", httpsBase + WHOIS_FILENAME); - services.add(whois); - } - } - - private static boolean hasServiceWithId(JsonArray services, String did, - String fragment) { - String absolute = did + fragment; - for (JsonElement el : services) { - if (!el.isJsonObject()) { - continue; - } - JsonElement idEl = el.getAsJsonObject().get("id"); - if (idEl == null || idEl.isJsonNull()) { - continue; - } - String id = idEl.getAsString(); - if (fragment.equals(id) || absolute.equals(id)) { - return true; - } - } - return false; - } - private static void addAlsoKnownAs(JsonObject doc, String didWebVh, String didWeb) { Set seen = new LinkedHashSet<>(); diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/didweb/ImplicitServices.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/didweb/ImplicitServices.java new file mode 100644 index 0000000..d8bce6d --- /dev/null +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/didweb/ImplicitServices.java @@ -0,0 +1,103 @@ +package io.github.decentralizedidentity.didwebvh.core.didweb; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.github.decentralizedidentity.didwebvh.core.url.DidToHttpsTransformer; + +/** + * Adds the implicit {@code #files} and {@code #whois} services defined for + * {@code did:webvh} by spec §3.8 and §3.9 to a DID Document. + * + *

These services are defined by the method itself and MUST be present in + * the resolved DID Document unless the controller has overridden them by + * declaring services with the same id (or fragment) explicitly. + */ +public final class ImplicitServices { + + private static final String FILES_ID = "#files"; + private static final String WHOIS_ID = "#whois"; + private static final String WELL_KNOWN_SEGMENT = "/.well-known/"; + private static final String DID_LOG_FILENAME = "did.jsonl"; + private static final String WHOIS_FILENAME = "whois.vp"; + + private ImplicitServices() { + } + + /** + * Mutate {@code doc} in place, appending the implicit {@code #files} and + * {@code #whois} services if not already present (compared by service + * fragment, either as bare fragment or fully qualified id). + * + * @param doc the DID Document JSON object (mutated in place) + * @param did the DID string used to construct fully-qualified service ids + */ + public static void addTo(JsonObject doc, String did) { + if (doc == null || did == null || did.isEmpty()) { + return; + } + String httpsBase = httpsBase(did); + + JsonArray services; + JsonElement existing = doc.get("service"); + if (existing != null && existing.isJsonArray()) { + services = existing.getAsJsonArray(); + } else { + services = new JsonArray(); + doc.add("service", services); + } + + if (!hasServiceWithId(services, did, FILES_ID)) { + JsonObject files = new JsonObject(); + files.addProperty("id", did + FILES_ID); + files.addProperty("type", "relativeRef"); + files.addProperty("serviceEndpoint", httpsBase); + services.add(files); + } + + if (!hasServiceWithId(services, did, WHOIS_ID)) { + JsonObject whois = new JsonObject(); + whois.addProperty("@context", + "https://identity.foundation/linked-vp/contexts/v1"); + whois.addProperty("id", did + WHOIS_ID); + whois.addProperty("type", "LinkedVerifiablePresentation"); + whois.addProperty("serviceEndpoint", httpsBase + WHOIS_FILENAME); + services.add(whois); + } + } + + /** + * Compute the HTTPS base URL for implicit service endpoints: the + * DID-to-HTTPS URL with the trailing {@code did.jsonl} filename removed + * and any {@code .well-known/} segment stripped. + */ + static String httpsBase(String did) { + String didLogUrl = DidToHttpsTransformer.toHttpsUrl(did); + String base = didLogUrl.substring(0, + didLogUrl.length() - DID_LOG_FILENAME.length()); + int idx = base.indexOf(WELL_KNOWN_SEGMENT); + if (idx >= 0) { + base = base.substring(0, idx + 1) + base.substring(idx + WELL_KNOWN_SEGMENT.length()); + } + return base; + } + + private static boolean hasServiceWithId(JsonArray services, String did, + String fragment) { + String absolute = did + fragment; + for (JsonElement el : services) { + if (!el.isJsonObject()) { + continue; + } + JsonElement idEl = el.getAsJsonObject().get("id"); + if (idEl == null || idEl.isJsonNull()) { + continue; + } + String id = idEl.getAsString(); + if (fragment.equals(id) || absolute.equals(id)) { + return true; + } + } + return false; + } +} diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessor.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessor.java index 8e2fa1d..62e7662 100644 --- a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessor.java +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessor.java @@ -1,8 +1,10 @@ package io.github.decentralizedidentity.didwebvh.core.resolve; +import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import io.github.decentralizedidentity.didwebvh.core.ResolutionException; import io.github.decentralizedidentity.didwebvh.core.ValidationException; +import io.github.decentralizedidentity.didwebvh.core.didweb.ImplicitServices; import io.github.decentralizedidentity.didwebvh.core.model.DidDocument; import io.github.decentralizedidentity.didwebvh.core.model.JsonSupport; import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; @@ -66,7 +68,12 @@ ResolveResult process(String didLogContent, String witnessContent, ResolveResult result = new ResolveResult().setMetadata(metadata); if (!Boolean.TRUE.equals(selectedParameters.getDeactivated())) { - result.setDidDocument(new DidDocument(selected.getState().deepCopy())); + // Spec §3.8 and §3.9: the resolved DID Document MUST include the + // implicit #files and #whois services unless the controller has + // already defined explicit services with the same id. + JsonObject state = selected.getState().deepCopy(); + ImplicitServices.addTo(state, selected.getState().get("id").getAsString()); + result.setDidDocument(new DidDocument(state)); } return result; } diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/update/DeactivateDidOperation.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/update/DeactivateDidOperation.java index 8f2ab28..f06033a 100644 --- a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/update/DeactivateDidOperation.java +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/update/DeactivateDidOperation.java @@ -37,8 +37,11 @@ static UpdateDidResult execute(DeactivateDidConfig config) { LogEntry previous = config.getExistingState().getLastEntry(); if (preRotationActive) { - // Intermediate entry: signed by current key (signer), reveals the next keys - // so that pre-rotation constraints are satisfied, then clears nextKeyHashes. + // Intermediate entry: reveals the next key (committed via nextKeyHashes + // in the previous entry) and clears nextKeyHashes to turn off pre-rotation. + // Per spec §3.7.5, while pre-rotation is active each entry is signed by its + // own updateKeys — so the intermediate is signed by the next-rotation signer, + // not the prior signer. String nextMultikey = ProofVerifier.extractMultikey( config.getNextRotationSigner().verificationMethod()); Parameters intermediate = new Parameters() @@ -46,7 +49,7 @@ static UpdateDidResult execute(DeactivateDidConfig config) { .setNextKeyHashes(Collections.emptyList()); LogEntry intermediateEntry = UpdateDidOperation.buildEntry( previous, - config.getSigner(), + config.getNextRotationSigner(), null, intermediate); newEntries.add(intermediateEntry); diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidator.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidator.java index c1a433c..7305d9c 100644 --- a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidator.java +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidator.java @@ -91,9 +91,21 @@ public ValidationResult validate(List entries, String expectedDid) { "missing proof at entry " + (i + 1), activeParams); } DataIntegrityProof proof = entry.getProof().get(0); - // Authorization uses PREVIOUS active keys for i>0 so that key-rotation entries - // are authorized by the old key (the new keys only take effect after validation). - List signingKeys = (i == 0) + // Spec §3.7.5 (lines 1071-1082): the "active updateKeys" that must + // authorize an entry depends on whether Key Pre-Rotation is active + // for this entry: + // • First entry: the entry's own updateKeys. + // • No pre-rotation: the previous entry's updateKeys + // (the new keys only take effect after validation). + // • Pre-rotation active (previous entry committed a non-empty + // nextKeyHashes): the current entry's own updateKeys. The + // previous entry's nextKeyHashes already pre-committed to those + // keys, so they are the authorized signers for this rotation. + List prevNextKeyHashes = activeParams.getNextKeyHashes(); + boolean preRotationActive = i > 0 + && prevNextKeyHashes != null + && !prevNextKeyHashes.isEmpty(); + List signingKeys = (i == 0 || preRotationActive) ? newActiveParams.getUpdateKeys() : activeParams.getUpdateKeys(); if (signingKeys == null || signingKeys.isEmpty()) { return ValidationResult.failure(i - 1, i, @@ -128,19 +140,34 @@ public ValidationResult validate(List entries, String expectedDid) { if (expectedDid == null || docId.equals(expectedDid)) { didFound = true; } + // Portability: parameters.scid is the cryptographic anchor and + // MUST NOT change across the log. The SCID component of + // state.id is the third colon-delimited segment of the DID URL + // (did:webvh::…). Portability moves host/path only; + // any entry whose state.id SCID differs from parameters.scid is + // rejected, regardless of portable / alsoKnownAs. + String activeScid = newActiveParams.getScid(); + if (activeScid != null) { + String[] parts = docId.split(":"); + if (parts.length >= 3 && !activeScid.equals(parts[2])) { + return ValidationResult.failure(i - 1, i, + "state.id SCID component does not match parameters.scid at entry " + + (i + 1), activeParams); + } + } } - // 10. Pre-rotation: if previous entry set nextKeyHashes and this entry rotates keys - if (i > 0 && entryParams.getUpdateKeys() != null) { - List prevNextKeyHashes = activeParams.getNextKeyHashes(); - if (prevNextKeyHashes != null && !prevNextKeyHashes.isEmpty()) { - for (String key : entryParams.getUpdateKeys()) { - String hash = PreRotationHashGenerator.generateHash(key); - if (!prevNextKeyHashes.contains(hash)) { - return ValidationResult.failure(i - 1, i, - "updateKey hash not in previous nextKeyHashes at entry " + (i + 1), - activeParams); - } + // 10. Pre-rotation: if previous entry set nextKeyHashes and this entry rotates keys, + // the new updateKeys must hash-match the previously committed nextKeyHashes + // (spec §3.7.7, lines 1119-1123). The updateKeys-required guard is enforced + // in validateParameters before we ever get here. + if (preRotationActive && entryParams.getUpdateKeys() != null) { + for (String key : entryParams.getUpdateKeys()) { + String hash = PreRotationHashGenerator.generateHash(key); + if (!prevNextKeyHashes.contains(hash)) { + return ValidationResult.failure(i - 1, i, + "updateKey hash not in previous nextKeyHashes at entry " + (i + 1), + activeParams); } } } @@ -207,6 +234,17 @@ && compareMethod(entryDelta.getMethod(), prevActive.getMethod()) < 0) { && !Boolean.TRUE.equals(prevActive.getPortable())) { return "portable can only be set to true in first entry"; } + // Pre-rotation: if the previous entry committed nextKeyHashes, + // this entry MUST present a non-empty updateKeys. Otherwise the + // pre-rotation guarantee is defeated by inheriting the old keys. + // (Spec §3.7.7; see negative-pre-rotation-omit-updatekeys.) + List prevNextHashes = prevActive.getNextKeyHashes(); + if (prevNextHashes != null && !prevNextHashes.isEmpty() + && (entryDelta.getUpdateKeys() == null + || entryDelta.getUpdateKeys().isEmpty())) { + return "updateKeys MUST be present and non-empty when previous entry " + + "committed nextKeyHashes (pre-rotation active)"; + } } // Witness config bounds check – only applies when witnesses are actually configured diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidator.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidator.java index 053c7a2..b460712 100644 --- a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidator.java +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidator.java @@ -4,13 +4,18 @@ import io.github.decentralizedidentity.didwebvh.core.model.DataIntegrityProof; import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; import io.github.decentralizedidentity.didwebvh.core.model.Parameters; +import io.github.decentralizedidentity.didwebvh.core.model.VersionId; import io.github.decentralizedidentity.didwebvh.core.signing.ProofVerifier; import io.github.decentralizedidentity.didwebvh.core.witness.WitnessConfig; import io.github.decentralizedidentity.didwebvh.core.witness.WitnessEntry; import io.github.decentralizedidentity.didwebvh.core.witness.WitnessProofCollection; import io.github.decentralizedidentity.didwebvh.core.witness.WitnessProofEntry; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** Validates witness proofs for entries that require witnessing. */ public final class WitnessValidator { @@ -18,8 +23,17 @@ public final class WitnessValidator { /** * Validate witness proofs for entries starting at {@code fromEntryIndex}. * - *

For each entry at or after {@code fromEntryIndex} where witnessing is active, - * verifies that at least {@code threshold} valid proofs exist from authorized witnesses. + *

Spec §3.7.8 (lines 1244-1247, 1286-1304): a witness proof at versionId V + * implicitly approves every prior log entry. The DID Controller is expected + * to prune {@code did-witness.json} so only the latest proof per witness + * remains, and resolvers MUST accept this. Proofs whose {@code versionId} is + * not present in the published log are ignored. + * + *

For each log entry with active witnessing, this validator counts the + * distinct authorized witnesses that have signed at least one valid proof + * at a versionId in the published log whose version number is greater than + * or equal to the entry's. If that count meets the entry's threshold the + * entry is witnessed. * * @param entries the full log entry list (used to accumulate parameters) * @param witnessProofs the witness proof collection from {@code did-witness.json} @@ -33,7 +47,9 @@ public WitnessValidationResult validate(List entries, return WitnessValidationResult.failure(-1, "witness proofs collection is null"); } - // Reconstruct active parameters up to fromEntryIndex + Map publishedVersionNumbers = publishedVersionNumbers(entries); + List verifiedProofs = verifyProofs(witnessProofs, publishedVersionNumbers); + Parameters activeParams = new Parameters(); for (int i = 0; i < fromEntryIndex && i < entries.size(); i++) { Parameters ep = entries.get(i).getParameters(); @@ -46,66 +62,128 @@ public WitnessValidationResult validate(List entries, LogEntry entry = entries.get(i); Parameters entryParams = entry.getParameters() != null ? entry.getParameters() : new Parameters(); + WitnessConfig priorConfig = activeParams.getWitness(); activeParams = activeParams.merge(entryParams); + WitnessConfig afterConfig = activeParams.getWitness(); - WitnessConfig witnessConfig = activeParams.getWitness(); - if (witnessConfig == null || !witnessConfig.isActive()) { + // Spec §3.7.5 (lines 884-889): when the witness parameter is set to + // {} while witnesses were previously active, that log entry MUST be + // witnessed by the prior witnesses. Otherwise an attacker could + // legitimately disable witnessing in a forged entry and slip the + // change past the resolver. Skip only when no witnessing was + // active either side of this entry. + boolean priorActive = priorConfig != null && priorConfig.isActive(); + boolean afterActive = afterConfig != null && afterConfig.isActive(); + boolean witnessChanged = entryParams.getWitness() != null; + WitnessConfig witnessConfig; + if (afterActive) { + // Per spec, "from {} to active" means the new config is + // immediately active and approves this entry. + witnessConfig = afterConfig; + } else if (priorActive && witnessChanged) { + // Transition from active to {}: the prior witnesses must + // approve this transition. + witnessConfig = priorConfig; + } else { continue; } - WitnessProofEntry witnessProofEntry = - findWitnessProof(witnessProofs, entry.getVersionId()); - if (witnessProofEntry == null) { - return WitnessValidationResult.failure(i, - "missing witness proof for entry " + entry.getVersionId()); - } - - // The signed document is {"versionId": ""} - JsonObject signedDoc = new JsonObject(); - signedDoc.addProperty("versionId", entry.getVersionId()); - - List authorizedWitnesses = witnessConfig.getWitnesses(); - int validProofCount = 0; + int entryVersion = VersionId.parse(entry.getVersionId()).getVersionNumber(); + List authorized = witnessConfig.getWitnesses(); - for (DataIntegrityProof proof : witnessProofEntry.getProof()) { - String multikey = ProofVerifier.extractMultikey(proof.getVerificationMethod()); - String witnessDid = "did:key:" + multikey; - if (!isAuthorizedWitness(witnessDid, authorizedWitnesses)) { + Set approvers = new HashSet<>(); + for (VerifiedProof vp : verifiedProofs) { + if (vp.versionNumber < entryVersion) { continue; } - if (ProofVerifier.verify(proof, signedDoc)) { - validProofCount++; + String matched = matchAuthorizedWitness(vp.signerMultikey, authorized); + if (matched != null) { + approvers.add(matched); } } - if (validProofCount < witnessConfig.getThreshold()) { + if (approvers.size() < witnessConfig.getThreshold()) { return WitnessValidationResult.failure(i, "insufficient witness proofs for entry " + entry.getVersionId() + ": need " + witnessConfig.getThreshold() - + ", got " + validProofCount); + + ", got " + approvers.size()); } } return WitnessValidationResult.success(); } - private boolean isAuthorizedWitness(String witnessDid, - List authorizedWitnesses) { - for (WitnessEntry w : authorizedWitnesses) { - if (witnessDid.equals(w.getId())) { - return true; + /** Map from each entry's full versionId string to its parsed version number. */ + private static Map publishedVersionNumbers(List entries) { + Map out = new HashMap<>(); + for (LogEntry e : entries) { + out.put(e.getVersionId(), VersionId.parse(e.getVersionId()).getVersionNumber()); + } + return out; + } + + /** + * Verify every proof in the witness file once and keep the ones that + * (a) are published, (b) verify against {"versionId": ""}. Returns one + * entry per verified proof with the signer's multikey already extracted. + */ + private static List verifyProofs(WitnessProofCollection witnessProofs, + Map publishedVersionNumbers) { + List out = new java.util.ArrayList<>(); + for (WitnessProofEntry entry : witnessProofs.getEntries()) { + Integer versionNumber = publishedVersionNumbers.get(entry.getVersionId()); + if (versionNumber == null) { + // Spec line 1294: ignore proofs for unpublished entries. + continue; + } + JsonObject signedDoc = new JsonObject(); + signedDoc.addProperty("versionId", entry.getVersionId()); + if (entry.getProof() == null) { + continue; + } + for (DataIntegrityProof proof : entry.getProof()) { + if (!ProofVerifier.verify(proof, signedDoc)) { + continue; + } + String signerMultikey = ProofVerifier.extractMultikey(proof.getVerificationMethod()); + out.add(new VerifiedProof(versionNumber, signerMultikey)); } } - return false; + return out; } - private WitnessProofEntry findWitnessProof(WitnessProofCollection collection, - String versionId) { - for (WitnessProofEntry entry : collection.getEntries()) { - if (versionId.equals(entry.getVersionId())) { - return entry; + /** + * Return the canonical authorized-witness id matching {@code signerMultikey}, + * or {@code null} if not authorized. Accepts the spec form + * {@code did:key:<multikey>} and the bare-multikey form some + * implementations emit; comparison is on the multikey portion. + */ + private static String matchAuthorizedWitness(String signerMultikey, + List authorized) { + for (WitnessEntry w : authorized) { + if (signerMultikey.equals(stripDidKey(w.getId()))) { + return w.getId(); } } return null; } + + private static String stripDidKey(String witnessId) { + if (witnessId == null) { + return null; + } + String prefix = "did:key:"; + return witnessId.startsWith(prefix) ? witnessId.substring(prefix.length()) : witnessId; + } + + /** Pre-verified proof entry indexed by its (published) version number and signer. */ + private static final class VerifiedProof { + final int versionNumber; + final String signerMultikey; + + VerifiedProof(int versionNumber, String signerMultikey) { + this.versionNumber = versionNumber; + this.signerMultikey = signerMultikey; + } + } } diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/witness/WitnessConfig.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/witness/WitnessConfig.java index 59f81fc..2355f99 100644 --- a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/witness/WitnessConfig.java +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/witness/WitnessConfig.java @@ -1,14 +1,25 @@ package io.github.decentralizedidentity.didwebvh.core.witness; +import com.google.gson.annotations.JsonAdapter; + import java.util.Collections; import java.util.List; import java.util.Objects; /** Witness configuration: a threshold and a list of witnesses. */ +@JsonAdapter(WitnessConfigTypeAdapter.class) public final class WitnessConfig { - private final int threshold; - private final List witnesses; + private int threshold; + private List witnesses = Collections.emptyList(); + + // No-args constructor lets Gson populate the type via reflection without + // hitting sun.misc.Unsafe.allocateInstance, which would skip field + // initializers and leave `witnesses` null. Other implementations + // (Python, TS) serialise an empty `witness: {}` object when no witnesses + // are configured, so the deserializer must produce a usable empty config. + public WitnessConfig() { + } public WitnessConfig(int threshold, List witnesses) { this.threshold = threshold; diff --git a/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/witness/WitnessConfigTypeAdapter.java b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/witness/WitnessConfigTypeAdapter.java new file mode 100644 index 0000000..d381dc6 --- /dev/null +++ b/didwebvh-core/src/main/java/io/github/decentralizedidentity/didwebvh/core/witness/WitnessConfigTypeAdapter.java @@ -0,0 +1,78 @@ +package io.github.decentralizedidentity.didwebvh.core.witness; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Round-trips the spec's {@code witness} parameter: an empty witness config + * serialises as {@code {}} (matching Python/TS implementations), and a + * configured one as {@code {"threshold":N,"witnesses":[...]}}. Reading + * accepts either form, plus a missing field; writing emits the spec form so + * canonicalised hashes (SCID, entry hash, proof) align across implementations. + */ +final class WitnessConfigTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, WitnessConfig value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + out.beginObject(); + if (value.isActive()) { + out.name("threshold").value(value.getThreshold()); + out.name("witnesses").beginArray(); + for (WitnessEntry entry : value.getWitnesses()) { + out.beginObject(); + out.name("id").value(entry.getId()); + out.endObject(); + } + out.endArray(); + } + out.endObject(); + } + + @Override + public WitnessConfig read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + in.beginObject(); + int threshold = 0; + List witnesses = new ArrayList<>(); + while (in.hasNext()) { + String name = in.nextName(); + if ("threshold".equals(name)) { + threshold = in.nextInt(); + } else if ("witnesses".equals(name)) { + in.beginArray(); + while (in.hasNext()) { + in.beginObject(); + String id = null; + while (in.hasNext()) { + String field = in.nextName(); + if ("id".equals(field)) { + id = in.nextString(); + } else { + in.skipValue(); + } + } + in.endObject(); + witnesses.add(new WitnessEntry(id)); + } + in.endArray(); + } else { + in.skipValue(); + } + } + in.endObject(); + return new WitnessConfig(threshold, witnesses); + } +} diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/integration/TestVectorGenerator.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/integration/TestVectorGenerator.java index a0a4fac..ce31728 100644 --- a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/integration/TestVectorGenerator.java +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/integration/TestVectorGenerator.java @@ -183,13 +183,15 @@ private static void writePreRotationLog(Path outDir) throws IOException { .execute(); DidWebVhState state = DidWebVhState.from(create.getDid(), create.getLogEntry()); - // Rotate: reveal next key and commit another future rotation. + // Rotate: reveal next key and commit another future rotation. Per spec + // §3.7.5 the rotation entry is signed by its OWN updateKeys (the + // pre-committed `next` key), not the prior author key. String futureKeyHash = PreRotationHashGenerator.generateHash( multikeyOf(TestVectors.seededSigner(TestVectors.UPDATE_SEED))); Parameters rotation = new Parameters() .setUpdateKeys(Collections.singletonList(nextMultikey)) .setNextKeyHashes(Collections.singletonList(futureKeyHash)); - UpdateDidResult rot = DidWebVh.update(state, author) + UpdateDidResult rot = DidWebVh.update(state, next) .changedParameters(rotation) .execute(); for (LogEntry e : rot.getNewEntries()) { diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropEmptyWitnessObjectTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropEmptyWitnessObjectTest.java new file mode 100644 index 0000000..15456d4 --- /dev/null +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropEmptyWitnessObjectTest.java @@ -0,0 +1,53 @@ +package io.github.decentralizedidentity.didwebvh.core.interop; + +import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; +import io.github.decentralizedidentity.didwebvh.core.witness.WitnessConfig; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +/** + * Interop regression: Python and TS implementations serialize an empty + * {@code "witness": {}} object in parameters when no witnesses are configured. + * The spec permits this (an empty object means "no witnesses"); the Java + * library used to NPE because Gson bypasses the no-args constructor and left + * the internal {@code witnesses} list as {@code null}. + * + *

Source: swcurran/didwebvh-test-suite, vectors/basic-create/python/did.jsonl. + */ +class InteropEmptyWitnessObjectTest { + + @Test + void parsesEmptyWitnessObjectWithoutNpe() throws IOException { + String line = readVector("/interop/basic-create-python/did.jsonl").trim(); + + LogEntry entry = LogEntry.fromJsonLine(line); + + WitnessConfig witness = entry.getParameters().getWitness(); + assertThat(witness).isNotNull(); + assertThatCode(witness::isActive).doesNotThrowAnyException(); + assertThat(witness.isActive()).isFalse(); + assertThat(witness.getWitnesses()).isEmpty(); + assertThat(witness.getThreshold()).isZero(); + } + + private static String readVector(String resourcePath) throws IOException { + try (InputStream in = InteropEmptyWitnessObjectTest.class.getResourceAsStream(resourcePath)) { + if (in == null) { + throw new IllegalStateException("Missing test resource: " + resourcePath); + } + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int read; + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } +} diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropJavaEeccLogsTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropJavaEeccLogsTest.java new file mode 100644 index 0000000..10b3fbd --- /dev/null +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropJavaEeccLogsTest.java @@ -0,0 +1,74 @@ +package io.github.decentralizedidentity.didwebvh.core.interop; + +import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; +import io.github.decentralizedidentity.didwebvh.core.validate.LogChainValidator; +import io.github.decentralizedidentity.didwebvh.core.validate.ValidationResult; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Interop regression: java-eecc-produced logs were reported as failing with + * "versionTime must be after previous entry at entry N" — see GitHub issue #2. + * Investigation showed the root cause was canonicalization mismatch on the + * empty {@code witness: {}} parameter, which short-circuited validation before + * the versionTime check; once the witness round-trip was fixed, these logs + * validate. This test pins that behaviour. + * + *

Source: swcurran/didwebvh-test-suite, vectors/<name>/java-eecc/did.jsonl. + */ +class InteropJavaEeccLogsTest { + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = { + "basic-update", + "deactivate", + "key-rotation", + "multi-update", + "services", + "witness-update" + }) + void validatesJavaEeccLog(String vector) throws IOException { + String jsonl = readVector("/interop/" + vector + "-java-eecc/did.jsonl"); + List entries = parseJsonl(jsonl); + + ValidationResult result = new LogChainValidator().validate(entries, null); + + assertThat(result.isValid()) + .as("java-eecc %s log must validate; failure: %s", vector, result.getFailureReason()) + .isTrue(); + } + + private static List parseJsonl(String jsonl) { + List out = new ArrayList<>(); + for (String line : jsonl.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + out.add(LogEntry.fromJsonLine(trimmed)); + } + } + return out; + } + + private static String readVector(String resourcePath) throws IOException { + try (InputStream in = InteropJavaEeccLogsTest.class.getResourceAsStream(resourcePath)) { + if (in == null) { + throw new IllegalStateException("Missing test resource: " + resourcePath); + } + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int read; + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } +} diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropNegativeCrossDidWitnessReplayTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropNegativeCrossDidWitnessReplayTest.java new file mode 100644 index 0000000..36a2294 --- /dev/null +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropNegativeCrossDidWitnessReplayTest.java @@ -0,0 +1,80 @@ +package io.github.decentralizedidentity.didwebvh.core.interop; + +import io.github.decentralizedidentity.didwebvh.core.model.JsonSupport; +import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; +import io.github.decentralizedidentity.didwebvh.core.validate.WitnessValidationResult; +import io.github.decentralizedidentity.didwebvh.core.validate.WitnessValidator; +import io.github.decentralizedidentity.didwebvh.core.witness.WitnessProofCollection; +import io.github.decentralizedidentity.didwebvh.core.witness.WitnessProofEntry; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Interop regression: a witness proof from DID-B replayed verbatim into DID-A's + * {@code did-witness.json} must be rejected. The witness signing payload + * ({@code {"versionId": ""}}) carries no DID binding, so resolvers must + * filter proofs to those whose versionId is present in this log + * before counting them toward the threshold (spec §3.7.8). When the proof's + * versionId is not in this log, the entry's witness threshold cannot be met + * and resolution fails with {@code invalidDid}. + * + *

Source: swcurran/didwebvh-test-suite, + * vectors/negative-cross-did-witness-replay/ts/. + */ +class InteropNegativeCrossDidWitnessReplayTest { + + @Test + void replayedCrossDidWitnessProofIsRejected() throws IOException { + List entries = parseJsonl(readVector( + "/interop/negative-cross-did-witness-replay-ts/did.jsonl")); + WitnessProofCollection proofs = parseProofs(readVector( + "/interop/negative-cross-did-witness-replay-ts/did-witness.json")); + + WitnessValidationResult result = new WitnessValidator().validate(entries, proofs, 0); + + assertThat(result.isValid()) + .as("replayed cross-DID witness proof must be rejected; got: %s", + result.getFailureReason()) + .isFalse(); + } + + private static WitnessProofCollection parseProofs(String json) { + WitnessProofEntry[] arr = JsonSupport.compact().fromJson(json, WitnessProofEntry[].class); + return new WitnessProofCollection(Arrays.asList(arr)); + } + + private static List parseJsonl(String jsonl) { + List out = new ArrayList<>(); + for (String line : jsonl.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + out.add(LogEntry.fromJsonLine(trimmed)); + } + } + return out; + } + + private static String readVector(String resourcePath) throws IOException { + try (InputStream in = InteropNegativeCrossDidWitnessReplayTest.class + .getResourceAsStream(resourcePath)) { + if (in == null) { + throw new IllegalStateException("Missing test resource: " + resourcePath); + } + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int read; + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } +} diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropPreRotationConsumeTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropPreRotationConsumeTest.java new file mode 100644 index 0000000..41fd203 --- /dev/null +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropPreRotationConsumeTest.java @@ -0,0 +1,68 @@ +package io.github.decentralizedidentity.didwebvh.core.interop; + +import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; +import io.github.decentralizedidentity.didwebvh.core.validate.LogChainValidator; +import io.github.decentralizedidentity.didwebvh.core.validate.ValidationResult; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Interop regression: when Key Pre-Rotation is active, the spec + * (§3.7.5, lines 1078-1082) requires that each subsequent log entry be + * signed by the {@code updateKeys} of the current entry — not the + * previous one. The Rust and Java-EECC pre-rotation-consume vectors exercise + * this: every entry rotates to a new key whose hash was committed by the + * previous entry's {@code nextKeyHashes}, and the new entry is signed by its + * own key. + * + *

Source: swcurran/didwebvh-test-suite, vectors/pre-rotation-consume/<impl>/did.jsonl. + */ +class InteropPreRotationConsumeTest { + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = {"rust", "java-eecc"}) + void validatesPreRotationConsumeLog(String impl) throws IOException { + String jsonl = readVector("/interop/pre-rotation-consume-" + impl + "/did.jsonl"); + List entries = parseJsonl(jsonl); + + ValidationResult result = new LogChainValidator().validate(entries, null); + + assertThat(result.isValid()) + .as("pre-rotation-consume %s log must validate; failure: %s", impl, result.getFailureReason()) + .isTrue(); + } + + private static List parseJsonl(String jsonl) { + List out = new ArrayList<>(); + for (String line : jsonl.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + out.add(LogEntry.fromJsonLine(trimmed)); + } + } + return out; + } + + private static String readVector(String resourcePath) throws IOException { + try (InputStream in = InteropPreRotationConsumeTest.class.getResourceAsStream(resourcePath)) { + if (in == null) { + throw new IllegalStateException("Missing test resource: " + resourcePath); + } + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int read; + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } +} diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropRustWitnessProofsTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropRustWitnessProofsTest.java new file mode 100644 index 0000000..be68a87 --- /dev/null +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropRustWitnessProofsTest.java @@ -0,0 +1,77 @@ +package io.github.decentralizedidentity.didwebvh.core.interop; + +import io.github.decentralizedidentity.didwebvh.core.model.JsonSupport; +import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; +import io.github.decentralizedidentity.didwebvh.core.validate.WitnessValidationResult; +import io.github.decentralizedidentity.didwebvh.core.validate.WitnessValidator; +import io.github.decentralizedidentity.didwebvh.core.witness.WitnessProofCollection; +import io.github.decentralizedidentity.didwebvh.core.witness.WitnessProofEntry; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Interop regression: per spec §3.7.8 (lines 1244-1247, 1298-1304), a witness + * proof at versionId V implicitly approves all prior log entries; the DID + * Controller SHOULD prune did-witness.json to keep only the latest proof per + * witness. Resolvers MUST accept this. The Rust witness-update vector has a + * single proof entry at versionId 2 covering both log entries; the Rust + * witness-threshold vector encodes witness ids as bare multikeys rather than + * the {@code did:key:<multikey>} form. + * + *

Source: swcurran/didwebvh-test-suite, vectors/{witness-update,witness-threshold}/rust/. + */ +class InteropRustWitnessProofsTest { + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = {"witness-update", "witness-threshold"}) + void validatesRustWitnessProofs(String vector) throws IOException { + List entries = parseJsonl(readVector("/interop/" + vector + "-rust/did.jsonl")); + WitnessProofCollection proofs = parseProofs(readVector("/interop/" + vector + "-rust/did-witness.json")); + + WitnessValidationResult result = new WitnessValidator().validate(entries, proofs, 0); + + assertThat(result.isValid()) + .as("rust %s witness proofs must verify; failure: %s", vector, result.getFailureReason()) + .isTrue(); + } + + private static WitnessProofCollection parseProofs(String json) { + WitnessProofEntry[] arr = JsonSupport.compact().fromJson(json, WitnessProofEntry[].class); + return new WitnessProofCollection(Arrays.asList(arr)); + } + + private static List parseJsonl(String jsonl) { + List out = new ArrayList<>(); + for (String line : jsonl.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + out.add(LogEntry.fromJsonLine(trimmed)); + } + } + return out; + } + + private static String readVector(String resourcePath) throws IOException { + try (InputStream in = InteropRustWitnessProofsTest.class.getResourceAsStream(resourcePath)) { + if (in == null) { + throw new IllegalStateException("Missing test resource: " + resourcePath); + } + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int read; + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } +} diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropTsBasicUpdateTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropTsBasicUpdateTest.java new file mode 100644 index 0000000..2fdbb0d --- /dev/null +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/interop/InteropTsBasicUpdateTest.java @@ -0,0 +1,64 @@ +package io.github.decentralizedidentity.didwebvh.core.interop; + +import io.github.decentralizedidentity.didwebvh.core.model.LogEntry; +import io.github.decentralizedidentity.didwebvh.core.validate.LogChainValidator; +import io.github.decentralizedidentity.didwebvh.core.validate.ValidationResult; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Interop regression: TS implementation always serialises + * {@code "nextKeyHashes": []} and {@code "witness": {}} (plus + * {@code "watchers": []}) even when those features are not configured. + * The Java log-chain validator must treat these as the "feature not active" + * case, not reject them. + * + *

Source: swcurran/didwebvh-test-suite, vectors/basic-update/ts/did.jsonl. + */ +class InteropTsBasicUpdateTest { + + @Test + void validatesTsBasicUpdateLog() throws IOException { + String jsonl = readVector("/interop/basic-update-ts/did.jsonl"); + List entries = parseJsonl(jsonl); + + ValidationResult result = new LogChainValidator().validate(entries, null); + + assertThat(result.isValid()) + .as("TS basic-update log must validate; failure: %s", result.getFailureReason()) + .isTrue(); + } + + private static List parseJsonl(String jsonl) { + List out = new ArrayList<>(); + for (String line : jsonl.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + out.add(LogEntry.fromJsonLine(trimmed)); + } + } + return out; + } + + private static String readVector(String resourcePath) throws IOException { + try (InputStream in = InteropTsBasicUpdateTest.class.getResourceAsStream(resourcePath)) { + if (in == null) { + throw new IllegalStateException("Missing test resource: " + resourcePath); + } + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int read; + while ((read = in.read(buf)) > 0) { + out.write(buf, 0, read); + } + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } +} diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessorTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessorTest.java index cb05f47..5a4686f 100644 --- a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessorTest.java +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/resolve/LogProcessorTest.java @@ -28,6 +28,32 @@ class LogProcessorTest { private final LogProcessor processor = new LogProcessor(); + @Test + void resolvedDocumentIncludesImplicitFilesAndWhoisServices() { + Signer signer = makeTestSigner(); + CreateDidResult create = DidWebVh.create("example.com", signer).execute(); + + ResolveResult result = processor.process(create.getLogLine(), null, + create.getDid(), ResolveOptions.defaults()); + + com.google.gson.JsonArray services = result.getDidDocument().asJsonObject() + .getAsJsonArray("service"); + assertThat(services).isNotNull(); + assertThat(services).hasSize(2); + + JsonObject files = services.get(0).getAsJsonObject(); + assertThat(files.get("id").getAsString()).isEqualTo(create.getDid() + "#files"); + assertThat(files.get("type").getAsString()).isEqualTo("relativeRef"); + assertThat(files.get("serviceEndpoint").getAsString()) + .isEqualTo("https://example.com/"); + + JsonObject whois = services.get(1).getAsJsonObject(); + assertThat(whois.get("id").getAsString()).isEqualTo(create.getDid() + "#whois"); + assertThat(whois.get("type").getAsString()).isEqualTo("LinkedVerifiablePresentation"); + assertThat(whois.get("serviceEndpoint").getAsString()) + .isEqualTo("https://example.com/whois.vp"); + } + @Test void resolvesLatestEntryWithMetadata() { Signer signer = makeTestSigner(); @@ -189,6 +215,61 @@ void missingWitnessProofsThrowInvalidDid() { .hasMessageContaining("Witness proofs"); } + @Test + void crossDidWitnessReplayRejected() { + // negative-cross-did-witness-replay (didwebvh-test-suite): an attacker + // controls DID-A's hosting and lifts a genuine witness proof from + // DID-B (which shares a witness W with DID-A) into DID-A's + // did-witness.json. DID-A's later entries set witness:{} to disable + // witnessing so no DID-A entry will direct-verify against an + // entry-hash from DID-A's log. The resolver MUST reject the replayed + // proof because its versionId is not in DID-A's published log + // (spec §3.7.8). Expected error: invalidDid. + Signer authorA = makeTestSigner(); + Signer authorB = makeTestSigner(); + Signer sharedWitness = makeTestSigner(); + String sharedWitnessDid = "did:key:" + extractMultikey( + sharedWitness.verificationMethod()); + WitnessConfig sharedConfig = new WitnessConfig(1, + Collections.singletonList(new WitnessEntry(sharedWitnessDid))); + + // Build DID-B's 3rd entry to obtain a real versionId the witness + // genuinely signed over. We only need DID-B's versionId — we never + // resolve DID-B. + CreateDidResult createB = DidWebVh.create("other.example.com", authorB) + .witness(sharedConfig) + .execute(); + LogEntry b1 = createB.getLogEntry(); + LogEntry b2 = buildUpdateEntry(b1, authorB, null, null); + LogEntry b3 = buildUpdateEntry(b2, authorB, null, null); + WitnessProofCollection didBProofs = witnessProofs( + b3.getVersionId(), sharedWitness); + + // Build DID-A: entry 1 has witnessing on, entry 2 disables it, + // entry 3 inherits the disabled config. + CreateDidResult createA = DidWebVh.create("example.com", authorA) + .witness(sharedConfig) + .execute(); + LogEntry a1 = createA.getLogEntry(); + Parameters witnessOff = new Parameters().setWitness(WitnessConfig.empty()); + LogEntry a2 = buildUpdateEntry(a1, authorA, witnessOff, null); + LogEntry a3 = buildUpdateEntry(a2, authorA, null, null); + + String didALog = a1.toJsonLine() + "\n" + + a2.toJsonLine() + "\n" + + a3.toJsonLine() + "\n"; + + // did-witness.json for DID-A contains DID-B's proof verbatim — a real + // signature, but the versionId is from DID-B's log, not DID-A's. + String didAWitnessJson = JsonSupport.compact().toJson(didBProofs.getEntries()); + + assertThatThrownBy(() -> processor.process(didALog, didAWitnessJson, + createA.getDid(), ResolveOptions.defaults())) + .isInstanceOf(ResolutionException.class) + .satisfies(t -> assertThat(((ResolutionException) t).getError()) + .isEqualTo("invalidDid")); + } + @Test void problemDetailsUseDidwebvhProblemType() { ResolutionException exception = new ResolutionException( diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidatorTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidatorTest.java index 9e3c880..a5cdb52 100644 --- a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidatorTest.java +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/LogChainValidatorTest.java @@ -283,10 +283,13 @@ void preRotationValidChain() { .execute(); LogEntry e1 = create.getLogEntry(); - // Rotation is signed by the OLD signer (still active); it commits new key. + // Per spec §3.7.5 (lines 1078-1082), while pre-rotation is active each + // log entry is signed by the keys from the CURRENT entry — the new + // signer, not the old one. The previous entry pre-committed to this + // key via nextKeyHashes. Parameters rotateParams = new Parameters() .setUpdateKeys(Collections.singletonList(multikey2)); - LogEntry e2 = buildUpdateEntry(e1, create.getDid(), signer, rotateParams, null); + LogEntry e2 = buildUpdateEntry(e1, create.getDid(), signer2, rotateParams, null); ValidationResult vr = validator.validate(Arrays.asList(e1, e2), create.getDid()); assertThat(vr.isValid()).isTrue(); @@ -305,13 +308,15 @@ void preRotationWrongKeyFails() { .execute(); LogEntry e1 = create.getLogEntry(); - // Try to rotate to a different key (not the committed one) + // Try to rotate to a different key (not the committed one). Pre-rotation + // makes the new entry's own updateKeys the active signing keys, so the + // entry must be self-signed by signer3 to clear the authorization check + // and reach the rotation-hash mismatch (the actual interesting failure). Signer signer3 = makeTestSigner(); String multikey3 = extractMultikey(signer3.verificationMethod()); Parameters wrongRotate = new Parameters() .setUpdateKeys(Collections.singletonList(multikey3)); - // Sign with signer (still the active key) to get past auth check - LogEntry e2 = buildUpdateEntry(e1, create.getDid(), signer, wrongRotate, null); + LogEntry e2 = buildUpdateEntry(e1, create.getDid(), signer3, wrongRotate, null); ValidationResult vr = validator.validate(Arrays.asList(e1, e2), create.getDid()); @@ -504,6 +509,55 @@ static DataIntegrityProof signDocument(Signer s, JsonObject doc) { return proof.setProofValue(Base58Btc.encodeMultibase(signature)); } + @Test + void preRotationOmitUpdateKeysFails() { + // Spec §3.7.7 (and negative-pre-rotation-omit-updatekeys): when the + // previous entry committed nextKeyHashes, the next entry MUST present + // updateKeys. Omitting it must not let the old key keep signing. + Signer signer2 = makeTestSigner(); + String hash2 = PreRotationHashGenerator.generateHash( + extractMultikey(signer2.verificationMethod())); + + CreateDidResult create = DidWebVh.create("example.com", signer) + .nextKeyHashes(Collections.singletonList(hash2)) + .execute(); + LogEntry e1 = create.getLogEntry(); + + // Entry 2 omits updateKeys entirely AND is signed by the old key. + LogEntry e2 = buildUpdateEntry(e1, create.getDid(), signer, null, null); + + ValidationResult vr = validator.validate(Arrays.asList(e1, e2), create.getDid()); + + assertThat(vr.isValid()).isFalse(); + assertThat(vr.getFailureReason()).containsIgnoringCase("updateKeys"); + assertThat(vr.getFailureReason()).containsIgnoringCase("pre-rotation"); + } + + @Test + void portableScidSwapFails() { + // Spec §portability (and negative-portable-scid-swap): SCID component + // of state.id MUST equal parameters.scid; portability changes + // host/path only. + CreateDidResult create = DidWebVh.create("example.com", signer) + .portable(true) + .execute(); + LogEntry e1 = create.getLogEntry(); + + // Build a second entry whose state.id has a different SCID segment. + JsonObject state2 = e1.getState().deepCopy(); + String originalId = state2.get("id").getAsString(); + String[] parts = originalId.split(":"); + parts[2] = "QmAttackerChosenSCID000000000000000000000000000"; + state2.addProperty("id", String.join(":", parts)); + + LogEntry e2 = buildUpdateEntry(e1, create.getDid(), signer, null, state2); + + ValidationResult vr = validator.validate(Arrays.asList(e1, e2), create.getDid()); + + assertThat(vr.isValid()).isFalse(); + assertThat(vr.getFailureReason()).containsIgnoringCase("SCID"); + } + @Test void noProofFails() { CreateDidResult create = DidWebVh.create("example.com", signer).execute(); diff --git a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidatorTest.java b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidatorTest.java index c239f61..438106c 100644 --- a/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidatorTest.java +++ b/didwebvh-core/src/test/java/io/github/decentralizedidentity/didwebvh/core/validate/WitnessValidatorTest.java @@ -133,7 +133,9 @@ void missingWitnessProofEntryFails() { Collections.singletonList(entry), proofs, 0); assertThat(result.isValid()).isFalse(); - assertThat(result.getFailureReason()).contains("missing witness proof"); + // Spec §3.7.8: proofs whose versionId is not in the published log are + // ignored. With no usable proofs, the threshold is unmet. + assertThat(result.getFailureReason()).contains("insufficient witness proofs"); } @Test @@ -173,6 +175,39 @@ void noWitnessConfigSkipsValidation() { assertThat(result.isValid()).isTrue(); } + @Test + void crossDidWitnessReplayRejected() { + // negative-cross-did-witness-replay: DID-A's first entry needs a + // witness proof. Attacker injects DID-B's genuine proof for a + // versionId from DID-B's log; later DID-A entries turn witnessing + // off so no later proof carries the threshold for entry 1. The + // resolver MUST ignore proofs whose versionId is not in DID-A's + // published log (spec §3.7.8) — leaving the threshold unmet. + WitnessConfig wc = new WitnessConfig(1, + Collections.singletonList(new WitnessEntry(witnessDid1))); + CreateDidResult create = DidWebVh.create("example.com", authorSigner) + .witness(wc) + .execute(); + LogEntry e1 = create.getLogEntry(); + + // Entry 2 disables witnessing for the remainder of the log. + Parameters witnessOff = new Parameters().setWitness(WitnessConfig.empty()); + LogEntry e2 = LogChainValidatorTest.buildUpdateEntry( + e1, create.getDid(), authorSigner, witnessOff, null); + + // The "lifted" proof is a real signature by the shared witness over + // a versionId from another DID's log — i.e., a versionId NOT present + // in this DID's log. + WitnessProofCollection foreignProofs = makeWitnessProofs( + "3-zForeignDidEntryHashThatDoesNotExistInThisLog", witnessSigner1); + + WitnessValidationResult result = validator.validate( + Arrays.asList(e1, e2), foreignProofs, 0); + + assertThat(result.isValid()).isFalse(); + assertThat(result.getFailureReason()).contains("insufficient witness proofs"); + } + @Test void fromEntryIndexRespected() { // Two entries; only second requires witnessing diff --git a/didwebvh-core/src/test/resources/interop/README.md b/didwebvh-core/src/test/resources/interop/README.md new file mode 100644 index 0000000..e2f606b --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/README.md @@ -0,0 +1,30 @@ +# Interop Test Vectors + +Vendored from the [did:webvh test suite](https://github.com/swcurran/didwebvh-test-suite). + +Upstream commit: `c11fda313dcc8ef0d1d50cff907fa754f33df73e` (2026-05-26) + +Each subdirectory tracks one interop regression discovered by the +multi-implementation test suite (see GitHub issue #2). Files are copied +verbatim from the upstream `vectors/` tree and exercised by JUnit tests under +`didwebvh-core/src/test/java/.../interop/`. + +| Directory | Source path in upstream | Exercises | +| --- | --- | --- | +| `basic-create-python/` | `vectors/basic-create/python/` | Empty `witness: {}` deserialization (no NPE) | +| `basic-update-ts/` | `vectors/basic-update/ts/` | TS log with `witness: {}` + `nextKeyHashes: []` + `watchers: []` round-trip (SCID, entry hash, proof) | +| `basic-update-java-eecc/` | `vectors/basic-update/java-eecc/` | java-eecc log validation | +| `deactivate-java-eecc/` | `vectors/deactivate/java-eecc/` | java-eecc deactivate log validation | +| `key-rotation-java-eecc/` | `vectors/key-rotation/java-eecc/` | java-eecc key-rotation log validation | +| `multi-update-java-eecc/` | `vectors/multi-update/java-eecc/` | java-eecc multi-update log validation | +| `services-java-eecc/` | `vectors/services/java-eecc/` | java-eecc services log validation | +| `witness-update-java-eecc/`| `vectors/witness-update/java-eecc/`| java-eecc witness-update log validation | +| `pre-rotation-consume-rust/` | `vectors/pre-rotation-consume/rust/` | Pre-rotation: each entry signed by its own (committed) updateKeys | +| `pre-rotation-consume-java-eecc/`| `vectors/pre-rotation-consume/java-eecc/`| Pre-rotation: each entry signed by its own (committed) updateKeys | +| `witness-update-rust/` | `vectors/witness-update/rust/` | Witness proof pruning: single latest proof covers all prior entries | +| `witness-threshold-rust/` | `vectors/witness-threshold/rust/` | Witness id as bare multikey (without `did:key:` prefix) | +| `negative-cross-did-witness-replay-ts/` | `vectors/negative-cross-did-witness-replay/ts/` | Replayed cross-DID witness proof rejected: spec §3.7.5 (lines 884-889) requires the entry that disables witnessing to itself be witnessed by the prior witnesses | + +To refresh: bump the SHA above, re-download the listed files +(`curl -sSL https://raw.githubusercontent.com/swcurran/didwebvh-test-suite//`), +run `./mvnw -pl didwebvh-core verify`, and reconcile any test deltas. diff --git a/didwebvh-core/src/test/resources/interop/basic-create-python/did.jsonl b/didwebvh-core/src/test/resources/interop/basic-create-python/did.jsonl new file mode 100644 index 0000000..4273d20 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/basic-create-python/did.jsonl @@ -0,0 +1 @@ +{"versionId": "1-QmWFwGhwjwRBMcQRzHHSQwpebk7iQ8CKd8rihrzbU1BGRt", "versionTime": "2000-01-01T00:00:00Z", "parameters": {"updateKeys": ["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"], "portable": false, "watchers": [], "witness": {}, "deactivated": false, "method": "did:webvh:1.0", "scid": "QmXhVjFG6EBTosDastaaHMRypm2qSv4SMGctADsx878Yux"}, "state": {"@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1"], "id": "did:webvh:QmXhVjFG6EBTosDastaaHMRypm2qSv4SMGctADsx878Yux:example.com", "controller": "did:webvh:QmXhVjFG6EBTosDastaaHMRypm2qSv4SMGctADsx878Yux:example.com", "verificationMethod": [{"type": "Multikey", "publicKeyMultibase": "z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG", "purpose": "authentication", "id": "did:webvh:QmXhVjFG6EBTosDastaaHMRypm2qSv4SMGctADsx878Yux:example.com#P5RDjVJG"}], "authentication": ["did:webvh:QmXhVjFG6EBTosDastaaHMRypm2qSv4SMGctADsx878Yux:example.com#P5RDjVJG"], "assertionMethod": [], "keyAgreement": [], "capabilityDelegation": [], "capabilityInvocation": []}, "proof": [{"type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG", "created": "2000-01-01T00:00:00Z", "proofPurpose": "assertionMethod", "proofValue": "z33ApmedSA2ebP5dknMvrUvZLMB1UoNtaSu6bZkRVELao2QxwFB1pSSQQ4MrLidZ8jXCCic8Y4CqMAiUrmrnN3tyg"}]} diff --git a/didwebvh-core/src/test/resources/interop/basic-update-java-eecc/did.jsonl b/didwebvh-core/src/test/resources/interop/basic-update-java-eecc/did.jsonl new file mode 100644 index 0000000..ea9a206 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/basic-update-java-eecc/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmWc66oUVWRAdjUGAay4SAtdXL5721A8awa8ntha5TUrnJ","versionTime":"2026-05-26T18:45:06Z","parameters":{"method":"did:webvh:1.0","scid":"QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z46SCtQSKwSY1PQfsgmoJi9wfts4vNCivuJXey1RNcc7bmNVbikTf1ud2AS2dEKmrp2H3C4KiyuZWBYobgFZoVSdX"}]} +{"versionId":"2-QmeJtPUqLYmrzPoMDbnADCzkp1baK39NvSCM6GoKnoaFgB","versionTime":"2026-05-26T18:45:07Z","parameters":{},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"],"alsoKnownAs":["did:web:example.com"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z5bmu7Ta6Tzmff47RhKd5G3WLXnC5NSLsCvehcNEW22ptdFUJ3P8GXUfGerqWPprodq2YRo7Gq6XAPQb2T2cDwq5B"}]} diff --git a/didwebvh-core/src/test/resources/interop/basic-update-ts/did.jsonl b/didwebvh-core/src/test/resources/interop/basic-update-ts/did.jsonl new file mode 100644 index 0000000..b4b9e38 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/basic-update-ts/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmPFhMuZH9gjY2JZgyyrgRuFTywQ4mDhoKGVoGE8uy7hFD","versionTime":"2000-01-01T00:00:00Z","parameters":{"method":"did:webvh:1.0","scid":"Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"portable":false,"nextKeyHashes":[],"watchers":[],"witness":{},"deactivated":false},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com","controller":"did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com","verificationMethod":[{"type":"Multikey","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","purpose":"authentication","id":"did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com#P5RDjVJG"}],"authentication":["did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com#P5RDjVJG"],"assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2000-01-01T00:00:00Z","proofPurpose":"assertionMethod","proofValue":"z3gfipj528cwTsP7aSSWMsPzA5uqSUGSN7WNzJQFf1WTvjpHf9Ftjk6StQmqqzjyjQT9xyqTjEsRp2jw4DBjcyqac"}]} +{"versionId":"2-QmXbbxspnFjjt5FX9QEdn8C6D8FZJsFceQdoHFTx89fyT4","versionTime":"2000-01-02T00:00:00Z","parameters":{"updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"nextKeyHashes":[],"witness":{},"watchers":[]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com","controller":"did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com","verificationMethod":[{"type":"Multikey","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","purpose":"authentication","id":"did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com#P5RDjVJG"}],"authentication":["did:webvh:Qmdxt11AjZewCNXX69bpEDobgjySeZ7eFwjf4tgpF6p2Dg:example.com#P5RDjVJG"],"assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"alsoKnownAs":["did:web:example.com"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2000-01-02T00:00:00Z","proofPurpose":"assertionMethod","proofValue":"z53PwdwPmSpQoRkumzCVS2buqmTA6GoqaeEyQXJmDPG6diKnctCpcepx8e9NEEobynmM51HMg33kMYPPPWX21rPoy"}]} diff --git a/didwebvh-core/src/test/resources/interop/deactivate-java-eecc/did.jsonl b/didwebvh-core/src/test/resources/interop/deactivate-java-eecc/did.jsonl new file mode 100644 index 0000000..06dd6c1 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/deactivate-java-eecc/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmWc66oUVWRAdjUGAay4SAtdXL5721A8awa8ntha5TUrnJ","versionTime":"2026-05-26T18:45:06Z","parameters":{"method":"did:webvh:1.0","scid":"QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z46SCtQSKwSY1PQfsgmoJi9wfts4vNCivuJXey1RNcc7bmNVbikTf1ud2AS2dEKmrp2H3C4KiyuZWBYobgFZoVSdX"}]} +{"versionId":"2-QmSitS2W7UGLaXHHTKGQvx3MvMUb5YqHv1x6sxCR6aqTUx","versionTime":"2026-05-26T18:45:07Z","parameters":{"updateKeys":[],"deactivated":true},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z35ALPgVCRCFPKCNZBM9AAjidFqmtqo6cYaU4tsn9DfpkMaRjSq9pvL7imRbz8FQ52kocmWZ2FBqTa1DyYouG2uU5"}]} diff --git a/didwebvh-core/src/test/resources/interop/key-rotation-java-eecc/did.jsonl b/didwebvh-core/src/test/resources/interop/key-rotation-java-eecc/did.jsonl new file mode 100644 index 0000000..83c3cad --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/key-rotation-java-eecc/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmWc66oUVWRAdjUGAay4SAtdXL5721A8awa8ntha5TUrnJ","versionTime":"2026-05-26T18:45:06Z","parameters":{"method":"did:webvh:1.0","scid":"QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z46SCtQSKwSY1PQfsgmoJi9wfts4vNCivuJXey1RNcc7bmNVbikTf1ud2AS2dEKmrp2H3C4KiyuZWBYobgFZoVSdX"}]} +{"versionId":"2-QmQ9PkTSedPFyjKgWKj6TvYExjkchkCC9WPisi99MTvcPN","versionTime":"2026-05-26T18:45:07Z","parameters":{"updateKeys":["z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#ufuXSdxf","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#ufuXSdxf"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z2epMpJjPtKN9wgNTP3BgEEUzaRqyWMuC6QFNjxxJke2nRJNcXFYNU9hiFnq3e3tyyxaKh9joBLNFypmC5Ru3FshR"}]} diff --git a/didwebvh-core/src/test/resources/interop/multi-update-java-eecc/did.jsonl b/didwebvh-core/src/test/resources/interop/multi-update-java-eecc/did.jsonl new file mode 100644 index 0000000..fd7adc3 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/multi-update-java-eecc/did.jsonl @@ -0,0 +1,3 @@ +{"versionId":"1-QmWc66oUVWRAdjUGAay4SAtdXL5721A8awa8ntha5TUrnJ","versionTime":"2026-05-26T18:45:06Z","parameters":{"method":"did:webvh:1.0","scid":"QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z46SCtQSKwSY1PQfsgmoJi9wfts4vNCivuJXey1RNcc7bmNVbikTf1ud2AS2dEKmrp2H3C4KiyuZWBYobgFZoVSdX"}]} +{"versionId":"2-QmeJtPUqLYmrzPoMDbnADCzkp1baK39NvSCM6GoKnoaFgB","versionTime":"2026-05-26T18:45:07Z","parameters":{},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"],"alsoKnownAs":["did:web:example.com"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z5bmu7Ta6Tzmff47RhKd5G3WLXnC5NSLsCvehcNEW22ptdFUJ3P8GXUfGerqWPprodq2YRo7Gq6XAPQb2T2cDwq5B"}]} +{"versionId":"3-QmbFthJC5iCmPW4VSQxgjeHYw5RgsU1LHG1xHkY4NcUYvs","versionTime":"2026-05-26T18:45:08Z","parameters":{},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmXq3qKkf4miH65SnSpkaMbAcXixyR6JU4yt8p4W8ftGWx:example.com#P5RDjVJG"],"alsoKnownAs":["did:web:example.com","did:web:example.org"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z4JY8rvUGM7C8S2PHqWaH7nZgmETtbdRWVP6F5kQjgs6qrMM7sqP2qJp5Ndq7yQn1VGQGbKhq6Uc5nF3YDZAVK33N"}]} diff --git a/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/did-witness.json b/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/did-witness.json new file mode 100644 index 0000000..e357337 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/did-witness.json @@ -0,0 +1,15 @@ +[ + { + "versionId": "1-QmW1kazgpSeCNX4kZghibxLU2ye8nr6dqADhQiTz3qPD1C", + "proof": [ + { + "type": "DataIntegrityProof", + "cryptosuite": "eddsa-jcs-2022", + "verificationMethod": "did:key:z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L#z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L", + "created": "2000-01-01T00:00:00Z", + "proofPurpose": "assertionMethod", + "proofValue": "zTsLwZHBByHgNnHKaFuUumacoruk5mW4KCgwL7hNdt9W1WcyyP3LoqJ7FA3YgdCZ2C7gb1CLQ75oiYvMbyjsTFjP" + } + ] + } +] diff --git a/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/did.jsonl b/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/did.jsonl new file mode 100644 index 0000000..85e8c22 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmW1kazgpSeCNX4kZghibxLU2ye8nr6dqADhQiTz3qPD1C","versionTime":"2000-01-01T00:00:00Z","parameters":{"method":"did:webvh:1.0","scid":"QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"portable":false,"nextKeyHashes":[],"watchers":[],"witness":{"threshold":1,"witnesses":[{"id":"did:key:z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L"}]},"deactivated":false},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com","controller":"did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com","verificationMethod":[{"type":"Multikey","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","purpose":"authentication","id":"did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com#P5RDjVJG"}],"authentication":["did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com#P5RDjVJG"],"assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2000-01-01T00:00:00Z","proofPurpose":"assertionMethod","proofValue":"z3QhC4dk9WzF4xgmDguyKMUueki4NyQJWPmxfd972mM7g4jhkzerX8x2gnz4dxQCUddia7SgLh16iwy949b3Xmhr"}]} +{"versionId":"2-QmQAJ4FG6Py6JJW1aioswVK5a2f3ziopnFAaRwj8WHmH7u","versionTime":"2000-01-02T00:00:00Z","parameters":{"updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"nextKeyHashes":[],"witness":{},"watchers":[]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com","controller":"did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com","verificationMethod":[{"type":"Multikey","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","purpose":"authentication","id":"did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com#P5RDjVJG"}],"authentication":["did:webvh:QmaaKkr6nu7uSTpjSfAr3r7xBezNZGpWu6Gwtgqr6A4ynC:example.com#P5RDjVJG"],"assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2000-01-02T00:00:00Z","proofPurpose":"assertionMethod","proofValue":"zDQZamyCJc1uLhWfqGNwXwCTEKcU3ubc8v4UHCKGKUTy2wrMky2kNBq5Ain8VHYiuqV2eGYxrpPdrUTTKh5dND6g"}]} diff --git a/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/resolutionResult.json b/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/resolutionResult.json new file mode 100644 index 0000000..5cbdf9c --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/negative-cross-did-witness-replay-ts/resolutionResult.json @@ -0,0 +1,7 @@ +{ + "didDocument": null, + "didDocumentMetadata": {}, + "didResolutionMetadata": { + "error": "invalidDid" + } +} diff --git a/didwebvh-core/src/test/resources/interop/pre-rotation-consume-java-eecc/did.jsonl b/didwebvh-core/src/test/resources/interop/pre-rotation-consume-java-eecc/did.jsonl new file mode 100644 index 0000000..e6f9209 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/pre-rotation-consume-java-eecc/did.jsonl @@ -0,0 +1,3 @@ +{"versionId":"1-QmUAtPKg1uXBpip5vcY8H35x7zBsBk1RzsmQXU7mh49Te3","versionTime":"2026-05-26T18:45:06Z","parameters":{"method":"did:webvh:1.0","scid":"QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"nextKeyHashes":["Qmf2V5jB2UwPcFL5bvmKed7VvY3CSQ1RXyDdtip7ufpQ3R"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","controller":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z4zGxiksYG8M5rLPeqceJp6mGbcBVd1gBSywNuvRSQ4kvnnGCnUXFRQvV94XYGYpm71X9km882y9S5LXJhvvaSDzJ"}]} +{"versionId":"2-QmXhtcqT5EWWo8k66UMmTC2b8Mk86RZsXNDGGcX9xhYaPq","versionTime":"2026-05-26T18:45:07Z","parameters":{"updateKeys":["z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"],"nextKeyHashes":["QmdP2WQEBfT4vht72FZ2p2X7airS3FxmaGuoHgHQoDW1u9"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","controller":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com#ufuXSdxf","type":"Multikey","controller":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","publicKeyMultibase":"z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"}],"authentication":["did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com#ufuXSdxf"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z4oztszLXroa9TzZfDkCiAgR7AY2tr5VhJZK39zyQNzEqKb6Gnguyc1c8y41C221Qe316Xhb135r6BgtMU2uW1k5B"}]} +{"versionId":"3-QmXuBz3MwEkM6XNfQHxLde6Ar5SY1iASHBhNi9QanHiijn","versionTime":"2026-05-26T18:45:08Z","parameters":{"updateKeys":["z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"],"nextKeyHashes":["QmPz1YVUT3mXMEixAWj8J6NkiQbskFmNu3DLYQLdynmrtR"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","controller":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com#MiLH3kDQ","type":"Multikey","controller":"did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com","publicKeyMultibase":"z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"}],"authentication":["did:webvh:QmWZCzqBbRzYvH4nWeVZ6gViAfRYNuci6aT8o7ajAJZzWe:example.com#MiLH3kDQ"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"zF7V6bmuYMuhoUtKqmXazvi8xJcvpkq1FgZ14VN1LY5Meexy4gpb7MZwQ7iERtuWfi4TPrqDALgXa71by5exxWuB"}]} diff --git a/didwebvh-core/src/test/resources/interop/pre-rotation-consume-rust/did.jsonl b/didwebvh-core/src/test/resources/interop/pre-rotation-consume-rust/did.jsonl new file mode 100644 index 0000000..221d567 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/pre-rotation-consume-rust/did.jsonl @@ -0,0 +1,3 @@ +{"versionId":"1-QmUPj8g2FxDTpSRJugYRXJjv8w4NRYXsDPiaSkZc2ZJ18p","versionTime":"2000-01-01T00:00:00Z","parameters":{"method":"did:webvh:1.0","scid":"QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"nextKeyHashes":["Qmf2V5jB2UwPcFL5bvmKed7VvY3CSQ1RXyDdtip7ufpQ3R"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"assertionMethod":[],"authentication":["did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com#P5RDjVJG"],"capabilityDelegation":[],"capabilityInvocation":[],"controller":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","id":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","keyAgreement":[],"verificationMethod":[{"controller":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","id":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com#P5RDjVJG","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","type":"Multikey"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-05-14T19:50:40Z","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","proofPurpose":"assertionMethod","proofValue":"z4iA8Bb81tqDyjzXGqES4opXXjyVn2vhc6vWjXC6fdgCx5mMQz3nQWFpDrbgdA2WVH3noy8AcrP8uFFQHkxZfBe8M"}]} +{"versionId":"2-QmRUBVSMad4vNeSq14193UzmyEnsPATo5YFZPeRA1f7tq3","versionTime":"2000-01-02T00:00:00Z","parameters":{"updateKeys":["z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"],"nextKeyHashes":["QmdP2WQEBfT4vht72FZ2p2X7airS3FxmaGuoHgHQoDW1u9"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"assertionMethod":[],"authentication":["did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com#ufuXSdxf"],"capabilityDelegation":[],"capabilityInvocation":[],"controller":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","id":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","keyAgreement":[],"verificationMethod":[{"controller":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","id":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com#ufuXSdxf","publicKeyMultibase":"z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf","type":"Multikey"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-05-14T19:50:40Z","verificationMethod":"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf","proofPurpose":"assertionMethod","proofValue":"zCCpct89Th2j8ELnAHnKYSpHGChiyi4aooJQnEX3UFRbVqvwS85ke3TBQaYQpEWyUwvXzuuvSH5zn7y5koUgGBui"}]} +{"versionId":"3-QmTa52uaWAABzmTTfaKnEqpxwYNeTvuT8C2TStGZonrGKy","versionTime":"2000-01-03T00:00:00Z","parameters":{"updateKeys":["z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"],"nextKeyHashes":["QmPz1YVUT3mXMEixAWj8J6NkiQbskFmNu3DLYQLdynmrtR"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"assertionMethod":[],"authentication":["did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com#MiLH3kDQ"],"capabilityDelegation":[],"capabilityInvocation":[],"controller":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","id":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","keyAgreement":[],"verificationMethod":[{"controller":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com","id":"did:webvh:QmUQVmu7yyX6x9jCUH863pBacUmg8Wuu4ZZtsdfv7Uv5iD:example.com#MiLH3kDQ","publicKeyMultibase":"z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ","type":"Multikey"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-05-14T19:50:40Z","verificationMethod":"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ","proofPurpose":"assertionMethod","proofValue":"z2ufgLc1eVP8biA9PHGff18mz5AGt5YziGxUwzLonk4UvwycU6hhwnsL8WPvNZYc73DC1UVMU3C5iEjV2y6zynxbn"}]} diff --git a/didwebvh-core/src/test/resources/interop/services-java-eecc/did.jsonl b/didwebvh-core/src/test/resources/interop/services-java-eecc/did.jsonl new file mode 100644 index 0000000..5e3bed4 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/services-java-eecc/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmSSGfHkCx7n7VpC1ACofw2wvr1SNFUKj69J5JptdbuGtN","versionTime":"2026-05-26T18:45:06Z","parameters":{"method":"did:webvh:1.0","scid":"QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"]},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com","controller":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com#P5RDjVJG"],"service":[{"id":"#linked-domain","type":"LinkedDomains","serviceEndpoint":"https://example.com"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z2eFCGcPywz1NCkNtYoDWStoPJQJvKRzJikQo47uBTdsQgawpiPbJCJvmezkcxZ8vAJLsTBv8ittx4qV654M26hQZ"}]} +{"versionId":"2-QmWujJ9nY2h9zDzivFUH8x9Lip7XGg4L2gY8R8G6xn1SWR","versionTime":"2026-05-26T18:45:07Z","parameters":{},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com","controller":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:QmW4gBYwEpZuqEPSMWd3j3q7T4sZDw5mcFEKeDM7zWmWLB:example.com#P5RDjVJG"],"service":[{"id":"#linked-domain","type":"LinkedDomains","serviceEndpoint":"https://example.com"},{"id":"#messaging","type":"DIDCommMessaging","serviceEndpoint":"https://example.com/didcomm"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z2zod7RDAM9nhsiXQEyrMy28DfpL8u1BGWyYXAP1o7go93kqdruAh2Foo4h3oVsQQQ1H2CoSdKpamritssKTCytjb"}]} diff --git a/didwebvh-core/src/test/resources/interop/witness-threshold-rust/did-witness.json b/didwebvh-core/src/test/resources/interop/witness-threshold-rust/did-witness.json new file mode 100644 index 0000000..94f640f --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/witness-threshold-rust/did-witness.json @@ -0,0 +1,15 @@ +[ + { + "versionId": "1-QmdDTDXMshdRwAbnN7b22BKAUy1CvQsq6y6zQqSP7reDJ7", + "proof": [ + { + "type": "DataIntegrityProof", + "cryptosuite": "eddsa-jcs-2022", + "created": "2026-05-14T19:50:40Z", + "verificationMethod": "did:key:z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L#z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L", + "proofPurpose": "assertionMethod", + "proofValue": "z4kxvBoDWKQ87AyuzjUYNkcohnyku8yH49FDHvo9Dpf8ktpmrpReXhWafxJ1QmDoJuMg3PUgQGTMmyoxyyyojKNqC" + } + ] + } +] \ No newline at end of file diff --git a/didwebvh-core/src/test/resources/interop/witness-threshold-rust/did.jsonl b/didwebvh-core/src/test/resources/interop/witness-threshold-rust/did.jsonl new file mode 100644 index 0000000..b4cdff6 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/witness-threshold-rust/did.jsonl @@ -0,0 +1 @@ +{"versionId":"1-QmdDTDXMshdRwAbnN7b22BKAUy1CvQsq6y6zQqSP7reDJ7","versionTime":"2000-01-01T00:00:00Z","parameters":{"method":"did:webvh:1.0","scid":"QmW9K6bJYn5LhixSSi7UKD3W7uNGeQtKKuZ9NzqYtUcK69","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"witness":{"threshold":1,"witnesses":[{"id":"z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L"}]}},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"assertionMethod":[],"authentication":["did:webvh:QmW9K6bJYn5LhixSSi7UKD3W7uNGeQtKKuZ9NzqYtUcK69:example.com#P5RDjVJG"],"capabilityDelegation":[],"capabilityInvocation":[],"controller":"did:webvh:QmW9K6bJYn5LhixSSi7UKD3W7uNGeQtKKuZ9NzqYtUcK69:example.com","id":"did:webvh:QmW9K6bJYn5LhixSSi7UKD3W7uNGeQtKKuZ9NzqYtUcK69:example.com","keyAgreement":[],"verificationMethod":[{"controller":"did:webvh:QmW9K6bJYn5LhixSSi7UKD3W7uNGeQtKKuZ9NzqYtUcK69:example.com","id":"did:webvh:QmW9K6bJYn5LhixSSi7UKD3W7uNGeQtKKuZ9NzqYtUcK69:example.com#P5RDjVJG","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","type":"Multikey"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-05-14T19:50:40Z","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","proofPurpose":"assertionMethod","proofValue":"zWJXqQPpbg2RF34Qp9bumPJz3vMNuCgpUk1NHZuhA9q6H5hByDrUBzZu2hcKqPUDN93AVfA54mWAQEu4H4mxxvUt"}]} diff --git a/didwebvh-core/src/test/resources/interop/witness-update-java-eecc/did.jsonl b/didwebvh-core/src/test/resources/interop/witness-update-java-eecc/did.jsonl new file mode 100644 index 0000000..9b9b34d --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/witness-update-java-eecc/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmZyfnYUNC8URUiwpmT6fmi4VFhvTiYjeZNZjbZWbFKpXN","versionTime":"2026-05-26T18:45:06Z","parameters":{"method":"did:webvh:1.0","scid":"Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"witness":{"threshold":2,"witnesses":[{"id":"did:key:z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L"},{"id":"did:key:z6MkjnHi6KJKx625x56sxudLoKdDVDABJ5VHHdXRFPUea7NP"}]}},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com","controller":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"zWZbcn2N7npojdoiCpqtxhZrsHQmYYi9rBQVcn5w4p26ymoE2SdYfVZupM42Rws1oic5dF8HVRxAMhLXx36xYy8c"}]} +{"versionId":"2-QmZ38kfnEGATj9Cyook7mTPjayNoHpbmeSwy8E6vYNdMaq","versionTime":"2026-05-26T18:45:07Z","parameters":{"witness":{"threshold":1,"witnesses":[{"id":"did:key:z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L"}]}},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com","controller":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com","assertionMethod":[],"keyAgreement":[],"capabilityDelegation":[],"capabilityInvocation":[],"verificationMethod":[{"id":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com#P5RDjVJG","type":"Multikey","controller":"did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"}],"authentication":["did:webvh:Qmcy68zXi6zWSxhchHQRyRYjbFoVeaRjHsF9wLXGBaCMgx:example.com#P5RDjVJG"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","created":"2026-05-26T18:45:06Z","proofPurpose":"assertionMethod","proofValue":"z4woggEbwgas4jYg9qxPWUdzDiYe7FCAADjgLTrQPN7J6Vbd9FEUZaXh9NVk8DAT9CziUHHkMY6vcLkiQg7mpS7Su"}]} diff --git a/didwebvh-core/src/test/resources/interop/witness-update-rust/did-witness.json b/didwebvh-core/src/test/resources/interop/witness-update-rust/did-witness.json new file mode 100644 index 0000000..5c1fd8b --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/witness-update-rust/did-witness.json @@ -0,0 +1,23 @@ +[ + { + "versionId": "2-Qmbs1akRcXtWH1sWTGGMD5DHdaKAJk7CTK5gP3hwPDEUpe", + "proof": [ + { + "type": "DataIntegrityProof", + "cryptosuite": "eddsa-jcs-2022", + "created": "2026-05-14T19:50:40Z", + "verificationMethod": "did:key:z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L#z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L", + "proofPurpose": "assertionMethod", + "proofValue": "z43FgUhcYtLScBx7JCuvfQPF7GqD9pNo17NurhrNSpyaaXpfXHbKxbdB1n4sxRfrEGgPBg5Gs2UdzxUNy3uqUPGbL" + }, + { + "type": "DataIntegrityProof", + "cryptosuite": "eddsa-jcs-2022", + "created": "2026-05-14T19:50:40Z", + "verificationMethod": "did:key:z6MkjnHi6KJKx625x56sxudLoKdDVDABJ5VHHdXRFPUea7NP#z6MkjnHi6KJKx625x56sxudLoKdDVDABJ5VHHdXRFPUea7NP", + "proofPurpose": "assertionMethod", + "proofValue": "z2t93XSkNLswTLRcYWYfT6VEoujCQa6BmLbp7CGQyxsSsHvvqkbJm4Bmu6DsQguUAaeo6Ddgs8wGxWzSNgXJQwuPu" + } + ] + } +] \ No newline at end of file diff --git a/didwebvh-core/src/test/resources/interop/witness-update-rust/did.jsonl b/didwebvh-core/src/test/resources/interop/witness-update-rust/did.jsonl new file mode 100644 index 0000000..c8c4a54 --- /dev/null +++ b/didwebvh-core/src/test/resources/interop/witness-update-rust/did.jsonl @@ -0,0 +1,2 @@ +{"versionId":"1-QmayLd7TkamnZhA8EbQCP6aUvU7YvGW1xW2iauRFC47ziP","versionTime":"2000-01-01T00:00:00Z","parameters":{"method":"did:webvh:1.0","scid":"QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8","updateKeys":["z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"],"witness":{"threshold":2,"witnesses":[{"id":"z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L"},{"id":"z6MkjnHi6KJKx625x56sxudLoKdDVDABJ5VHHdXRFPUea7NP"}]}},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"assertionMethod":[],"authentication":["did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com#P5RDjVJG"],"capabilityDelegation":[],"capabilityInvocation":[],"controller":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com","id":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com","keyAgreement":[],"verificationMethod":[{"controller":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com","id":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com#P5RDjVJG","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","type":"Multikey"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-05-14T19:50:40Z","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","proofPurpose":"assertionMethod","proofValue":"z5VBHsMVCKQRAbvgFrdjT2xjcm8qxyKtbTMdupiKrBpZHyks7YuX5UfsnX3XHzXYMsncTkpvvognXySAAwddhrW6j"}]} +{"versionId":"2-Qmbs1akRcXtWH1sWTGGMD5DHdaKAJk7CTK5gP3hwPDEUpe","versionTime":"2000-01-02T00:00:00Z","parameters":{"witness":{"threshold":1,"witnesses":[{"id":"z6Mkrv5Cm2XCLumMPTqooLTCw6YDf421d7VdTziwrZ8vNf4L"}]}},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"assertionMethod":[],"authentication":["did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com#P5RDjVJG"],"capabilityDelegation":[],"capabilityInvocation":[],"controller":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com","id":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com","keyAgreement":[],"verificationMethod":[{"controller":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com","id":"did:webvh:QmVArGEbmoYrzKp9wmpnCPYLjnvwHWhYC1jK8B8WCwgBd8:example.com#P5RDjVJG","publicKeyMultibase":"z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","type":"Multikey"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-05-14T19:50:40Z","verificationMethod":"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG","proofPurpose":"assertionMethod","proofValue":"z4DX91GNYFR2xfGpWcWKQR1piepfFhNPmhq1FvB4KBd8MeYeBJJeP1VyTkoteD7CHafActDHvo3k8H4DRwHkqjnri"}]} diff --git a/didwebvh-core/src/test/resources/test-vectors/pre-rotation-log.jsonl b/didwebvh-core/src/test/resources/test-vectors/pre-rotation-log.jsonl index 64be48a..58b7b55 100644 --- a/didwebvh-core/src/test/resources/test-vectors/pre-rotation-log.jsonl +++ b/didwebvh-core/src/test/resources/test-vectors/pre-rotation-log.jsonl @@ -1,2 +1,2 @@ -{"versionId":"1-QmT5Qo2RhdsxELYaA364TzVtNtRw3MQjy2hNTkXxbtUbDe","versionTime":"2026-04-20T14:56:01Z","parameters":{"method":"did:webvh:1.0","scid":"QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy","updateKeys":["z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"],"nextKeyHashes":["QmNaXxedyD9kNfajeHzeRqybxiV3s6ajVAQ5oNmeDPmx6q"]},"state":{"@context":"https://www.w3.org/ns/did/v1","id":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com","controller":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com","verificationMethod":[{"id":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK","type":"Multikey","controller":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com","publicKeyMultibase":"z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"}],"authentication":["did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"],"assertionMethod":["did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK","proofPurpose":"assertionMethod","created":"2026-04-20T14:56:01Z","proofValue":"z39ba6J3swdeidJRoTNeM1DdVMrP5EjLficinnYSJmTbJJnRWcEoHrYNCyfrP2CrUVeDXkEfLWH23vC9nJqonPJmt"}]} -{"versionId":"2-QmbdSE2Fki4pyn1x5ko7HzkNia8wC1UyNmmExxUjo8rG4b","versionTime":"2026-04-20T14:56:02Z","parameters":{"updateKeys":["z6MkojnT9YPG1rGnAfsp7GheGT8iXEYYZUgZyEDzMjXBjxsT"],"nextKeyHashes":["QmRNAiJ626yV1ajz68ZrSu4tdYUQwio2gSq49j8kigtbBi"]},"state":{"@context":"https://www.w3.org/ns/did/v1","id":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com","controller":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com","verificationMethod":[{"id":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK","type":"Multikey","controller":"did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com","publicKeyMultibase":"z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"}],"authentication":["did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"],"assertionMethod":["did:webvh:QmbE6xSTXgsmRJK4651HjoEk4nVo35EsmDTcEE2FkFhgxy:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK","proofPurpose":"assertionMethod","created":"2026-04-20T14:56:01Z","proofValue":"z28Au5NWzieDc1gods6i7VcyGUYQds9DdNeYSQaiU6g3Qjv3nuVAhjeqCPcMinugpM2JyK8C4y39ViD7yxEVApt4E"}]} +{"versionId":"1-QmPPJTcbGS9iLViSJV1TQCRyhTS5RAeWZ8PxxCqo7UhBVo","versionTime":"2026-05-27T09:14:22Z","parameters":{"method":"did:webvh:1.0","scid":"QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX","updateKeys":["z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"],"nextKeyHashes":["QmNaXxedyD9kNfajeHzeRqybxiV3s6ajVAQ5oNmeDPmx6q"]},"state":{"@context":"https://www.w3.org/ns/did/v1","id":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com","controller":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com","verificationMethod":[{"id":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK","type":"Multikey","controller":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com","publicKeyMultibase":"z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"}],"authentication":["did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"],"assertionMethod":["did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK","proofPurpose":"assertionMethod","created":"2026-05-27T09:14:22Z","proofValue":"z5kPyxyxer98gwxWkMh21Y3v8eigTFjLqPBqPejy5kwiiiauVKqribDjmvHABW6sexkEFfLCt6pmxY2juYHQJL4fJ"}]} +{"versionId":"2-QmamjEZx9jEPxhL16D1PMnXfB8G14T2UaWmBNT73Gak1pV","versionTime":"2026-05-27T09:14:23Z","parameters":{"updateKeys":["z6MkojnT9YPG1rGnAfsp7GheGT8iXEYYZUgZyEDzMjXBjxsT"],"nextKeyHashes":["QmRNAiJ626yV1ajz68ZrSu4tdYUQwio2gSq49j8kigtbBi"]},"state":{"@context":"https://www.w3.org/ns/did/v1","id":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com","controller":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com","verificationMethod":[{"id":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK","type":"Multikey","controller":"did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com","publicKeyMultibase":"z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"}],"authentication":["did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"],"assertionMethod":["did:webvh:QmdgCRqQ1w2MBn6pdxzragAfLdtrxW3tkut6yZyAEchAhX:example.com#z6MkrqE7t5Sepvay1uuKJfMpjzsJ1cBdqibx8b7C3VwiE7wK"]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkojnT9YPG1rGnAfsp7GheGT8iXEYYZUgZyEDzMjXBjxsT#z6MkojnT9YPG1rGnAfsp7GheGT8iXEYYZUgZyEDzMjXBjxsT","proofPurpose":"assertionMethod","created":"2026-05-27T09:14:22Z","proofValue":"z468s8dwvyrCZUD8iEXJEG5pSm9HfEezWaJSXgtpbTyi6pTPP3Ga212yobbdsCpMVLym9T6ENDFftCvXodiEsM2QH"}]} diff --git a/didwebvh-signing-local/pom.xml b/didwebvh-signing-local/pom.xml index 49789d6..393d43b 100644 --- a/didwebvh-signing-local/pom.xml +++ b/didwebvh-signing-local/pom.xml @@ -7,7 +7,7 @@ io.github.decentralized-identity didwebvh-java - 0.2.0-SNAPSHOT + 0.3.0-SNAPSHOT didwebvh-signing-local diff --git a/didwebvh-wizard/pom.xml b/didwebvh-wizard/pom.xml index 17a4786..93ae45e 100644 --- a/didwebvh-wizard/pom.xml +++ b/didwebvh-wizard/pom.xml @@ -7,7 +7,7 @@ io.github.decentralized-identity didwebvh-java - 0.2.0-SNAPSHOT + 0.3.0-SNAPSHOT didwebvh-wizard diff --git a/pom.xml b/pom.xml index fbf7e93..afc6dd1 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.decentralized-identity didwebvh-java - 0.2.0-SNAPSHOT + 0.3.0-SNAPSHOT pom didwebvh-java