From db16ede2bdcd808452bfaf84d781b4397111be27 Mon Sep 17 00:00:00 2001 From: Patrick St-Louis Date: Wed, 18 Feb 2026 22:31:50 -0500 Subject: [PATCH 1/2] docs: add migration guide for Indy to did:webvh AnonCreds issuance Adds a strategic guide for issuers transitioning from Indy-based AnonCreds credentials to did:webvh-rooted ones. Covers prerequisites, dual-issuance transition strategy, step-by-step setup, and impact on controllers and verifiers. Closes #3885 Signed-off-by: Patrick St-Louis Co-authored-by: Cursor --- docs/deploying/IndyToWebVHMigration.md | 311 +++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 312 insertions(+) create mode 100644 docs/deploying/IndyToWebVHMigration.md diff --git a/docs/deploying/IndyToWebVHMigration.md b/docs/deploying/IndyToWebVHMigration.md new file mode 100644 index 0000000000..659ade14aa --- /dev/null +++ b/docs/deploying/IndyToWebVHMigration.md @@ -0,0 +1,311 @@ +# Migrating AnonCreds Issuance from Indy to did:webvh + +This guide walks through the process of transitioning an ACA-Py Issuer from +publishing [AnonCreds] objects on a [Hyperledger Indy] ledger to publishing them +using the [did:webvh] method. The same ACA-Py instance can issue credentials +rooted in both methods simultaneously, so the migration can be done gradually +with no downtime or data loss. + +[AnonCreds]: https://www.hyperledger.org/projects/anoncreds +[Hyperledger Indy]: https://www.hyperledger.org/projects/hyperledger-indy +[did:webvh]: https://identity.foundation/didwebvh/ + +## Why Migrate? + +Indy-based AnonCreds requires a Hyperledger Indy blockchain network to store +schemas, credential definitions, and revocation registries. While this model is +proven and reliable, it ties issuers to a specific ledger network and its +governance. + +did:webvh offers an alternative Verifiable Data Registry (VDR) for AnonCreds +that is: + +- **Ledger-independent** -- AnonCreds objects are published as web-hosted + resources, removing the dependency on a blockchain network. +- **Web-native** -- Objects are hosted on standard web infrastructure and + resolved via HTTPS. +- **Trustworthy** -- A witness model provides cryptographic attestation of + published objects, replacing the endorser/steward trust model of Indy. +- **Portable** -- DIDs can optionally be configured for portability across + servers. +- **Interoperable** -- Credentials issued with did:webvh use the same AnonCreds + cryptography; verifiers process them identically to Indy-based credentials. + +For technical background on how ACA-Py supports multiple AnonCreds methods, see +[Publishing AnonCreds Objects To Other Ledgers/VDRs](../features/AnonCredsMethods.md). + +## Key Differences at a Glance + +| Aspect | Indy | did:webvh | +|---|---|---| +| **Identifier format** | `WgWx...:2:name:1.0` (legacy) or `did:indy:sovrin:WgWx...` | `did:webvh:{SCID}:domain:ns:id/resources/{digest}` | +| **Where objects live** | Indy ledger transactions | WebVH server-hosted resources with Data Integrity proofs | +| **Trust model** | Endorser/Steward signs ledger transactions | Witness attests resource publications | +| **Revocation tails files** | Uploaded to a tails server | Uploaded to a tails server (same approach) | +| **ACA-Py API endpoints** | `/anoncreds/*` | `/anoncreds/*` (same endpoints) | +| **Plugin required** | Built-in (`DIDIndyRegistry`, `LegacyIndyRegistry`) | External (`--plugin webvh`) | + +The important thing to note is that the ACA-Py admin API endpoints remain the +same. The only difference from a controller's perspective is the `issuerId` +used when creating objects and the format of the resulting identifiers. + +## Prerequisites + +Before starting the migration, ensure the following are in place: + +1. **`askar-anoncreds` wallet type** -- Your ACA-Py instance must be running + with `--wallet-type askar-anoncreds`. If you are still on the legacy `askar` + wallet type, complete the + [AnonCreds Controller Migration](AnoncredsControllerMigration.md) first. See + also [The askar-anoncreds Wallet Type](AnonCredsWalletType.md) for + background. + +2. **WebVH plugin installed** -- The did:webvh AnonCreds registry is provided + by the `webvh` plugin in [acapy-plugins]. Install it and load it with + `--plugin webvh`. See the + [WebVH plugin README](https://github.com/openwallet-foundation/acapy-plugins/tree/main/webvh) + for installation instructions. + +3. **WebVH server running** -- You need a running instance of + [didwebvh-server-py](https://github.com/decentralized-identity/didwebvh-server-py) + to host your DID documents and attested resources. + +4. **Witness configured** -- A witness agent (or self-witnessing configuration) + must be set up to attest DID registrations and resource uploads. See the + [WebVH plugin Configuration](https://github.com/openwallet-foundation/acapy-plugins/tree/main/webvh#configuration) + section for details. + +[acapy-plugins]: https://github.com/openwallet-foundation/acapy-plugins + +## What Can and Cannot Be Migrated + +**Cannot be migrated:** + +- Existing issued credentials remain Indy-based. Holders keep them as-is + and verifiers can continue to verify them. There is no mechanism to + "re-root" an already-issued AnonCreds credential to a different VDR. + +**Must be re-created under the new DID:** + +- Schemas (same attributes, new `issuerId`) +- Credential definitions (new cred def linked to the new schema) +- Revocation registries (automatically created by ACA-Py when a revocable + cred def is registered) + +**No data loss:** + +- Your wallet retains all Indy objects. ACA-Py supports multiple AnonCreds + registries simultaneously -- the correct registry is selected automatically + based on the identifier pattern of the object being accessed. + +## Migration Strategy: Dual-Issuance Transition + +The recommended approach is a phased transition where Indy and did:webvh +issuance run side-by-side: + +```mermaid +flowchart LR + subgraph phase1 ["Phase 1: Indy Only"] + IndyIssue["Issue Indy credentials"] + end + subgraph phase2 ["Phase 2: Dual Issuance"] + IndyIssue2["Continue Indy issuance"] + WebVHIssue["Begin did:webvh issuance"] + end + subgraph phase3 ["Phase 3: did:webvh Only"] + WebVHOnly["Issue did:webvh credentials only"] + end + phase1 --> phase2 --> phase3 +``` + +### Phase 1 -- Current State + +You are issuing Indy-based AnonCreds credentials. No changes yet. + +### Phase 2 -- Dual Issuance + +1. Set up the did:webvh infrastructure (server, witness, plugin) alongside + your existing Indy configuration. +2. Create a did:webvh DID for your issuer. +3. Re-register your schemas and credential definitions under the new DID. +4. Start issuing new credentials using the did:webvh credential definitions + for new holders or use cases. +5. Continue issuing Indy credentials for existing holders and verifiers who + have not yet added did:webvh resolution support. +6. Communicate the transition to your verifier ecosystem so they can prepare + to resolve did:webvh identifiers. + +### Phase 3 -- did:webvh Only + +1. Once your verifier ecosystem supports did:webvh resolution, stop issuing + new Indy credentials. +2. All new issuance uses did:webvh credential definitions. +3. Previously issued Indy credentials remain valid and verifiable for their + lifetime. They do not need to be re-issued. + +## Setting Up did:webvh Issuance + +The steps below provide a high-level overview. For detailed configuration +and API payloads, refer to the +[WebVH plugin README](https://github.com/openwallet-foundation/acapy-plugins/tree/main/webvh). + +### 1. Install and Configure the Plugin + +Add the `webvh` plugin to your ACA-Py startup: + +```bash +aca-py start \ + --wallet-type askar-anoncreds \ + --plugin webvh \ + ... +``` + +### 2. Configure Witness + +Configure your agent as a witness (self-witnessing) or connect to an external +witness agent: + +``` +POST /did/webvh/configuration +``` + +```json +{ + "server_url": "https://your-webvh-server.example.com", + "witness": true +} +``` + +For a controller that relies on an external witness, provide a +`witness_invitation` instead. See the plugin README for the full witness setup +flow. + +### 3. Create a did:webvh DID + +``` +POST /did/webvh/controller/create +``` + +```json +{ + "options": { + "namespace": "my-org", + "identifier": "issuer-01" + } +} +``` + +This creates a DID like `did:webvh:{SCID}:your-server.example.com:my-org:issuer-01`. + +### 4. Register Schemas + +Use the same `/anoncreds/schema` endpoint, but with the new did:webvh DID as +the `issuerId`: + +``` +POST /anoncreds/schema +``` + +```json +{ + "schema": { + "attrNames": ["name", "date", "degree"], + "issuerId": "did:webvh:{SCID}:your-server.example.com:my-org:issuer-01", + "name": "Example Credential", + "version": "1.0" + } +} +``` + +The resulting schema ID will be in the format +`did:webvh:{SCID}:your-server.example.com:my-org:issuer-01/resources/{content_digest}`. + +### 5. Create Credential Definitions + +``` +POST /anoncreds/credential-definition +``` + +```json +{ + "credential_definition": { + "issuerId": "did:webvh:{SCID}:your-server.example.com:my-org:issuer-01", + "schemaId": "", + "tag": "default" + }, + "options": { + "support_revocation": true, + "revocation_registry_size": 1000 + } +} +``` + +### 6. Revocation Registries + +If the credential definition supports revocation, ACA-Py automatically creates +and publishes two revocation registries, makes one active, and handles rotation +-- exactly the same as with Indy. No additional steps are needed. + +### 7. Issue Credentials + +Use the standard V2.0 issue-credential endpoints. The only difference is that +the `cred_def_id` in your offer references the did:webvh credential definition +created in step 5. + +## Impact on Controllers + +- **Same API, different identifiers** -- The `/anoncreds/*` endpoints are + identical for Indy and did:webvh. The only change is the `issuerId` you + supply and the format of the returned object IDs. +- **Track your credential definitions** -- During dual issuance, your + controller needs to know which cred def IDs are Indy-based and which are + did:webvh-based, so it can offer the correct credential type to each holder. +- **Webhook payloads** -- Webhook events for credential exchange will contain + the did:webvh identifier formats. Ensure your controller can handle both + formats during the transition. + +## Impact on Verifiers + +- **Resolution capability required** -- Verifiers must be able to resolve + did:webvh identifiers. This means they need either the `webvh` plugin loaded + or a universal resolver that supports did:webvh. +- **Accept both formats during transition** -- Proof requests should reference + both Indy and did:webvh credential definition IDs during the dual-issuance + period, or use attribute-based restrictions that are format-agnostic. +- **No protocol changes** -- The presentation exchange protocol (DIDComm v2, + present-proof v2) is identical regardless of the underlying VDR. AnonCreds + is AnonCreds -- the cryptographic operations and proof format are the same. + +## Frequently Asked Questions + +**Can I use the same schema attributes?** + +Yes. Register a new schema with identical `attrNames` under your did:webvh DID. +The schema content is the same; only the `issuerId` and resulting identifier +change. + +**Do holders need to do anything?** + +No. Existing Indy credentials in holders' wallets continue to work unchanged. +New credentials issued from did:webvh credential definitions are received and +stored normally -- holders do not need any special configuration. + +**Can I run Indy and did:webvh issuance simultaneously?** + +Yes. ACA-Py's AnonCreds registry automatically routes operations to the correct +registry based on the identifier pattern (`did:indy:*` or legacy Indy format +goes to the Indy registry; `did:webvh:*` goes to the WebVH registry). Both +registries can be active at the same time with no conflicts. + +**What happens to my existing Indy credentials after migration?** + +They remain valid and verifiable for as long as the Indy ledger they were +published on is operational. Revoking an Indy credential still works through +the Indy revocation registry. The migration only affects *new* issuance. + +**Do I need to keep my Indy ledger connection after Phase 3?** + +If you have outstanding Indy credentials that may need to be revoked, or if +verifiers may still need to resolve your Indy-based credential definitions, +yes. You can remove the Indy ledger connection only when all Indy credentials +have expired or been revoked and verifiers no longer need to resolve them. diff --git a/mkdocs.yml b/mkdocs.yml index 5da36b7146..459737f539 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -146,6 +146,7 @@ nav: - Databases: deploying/Databases.md - Persistent Queues and Caching: deploying/RedisPlugins.md - The askar-anoncreds Wallet Type: deploying/AnonCredsWalletType.md + - Migrating Issuance from Indy to did:webvh: deploying/IndyToWebVHMigration.md - Testing/Troubleshooting: - Running and Creating Unit Tests: testing/UnitTests.md - Integration Tests: testing/IntegrationTests.md From e00fe1b1598bcca03b4284e36846f1807ba1a629 Mon Sep 17 00:00:00 2001 From: Patrick St-Louis Date: Tue, 24 Feb 2026 09:26:48 -0500 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20reframe=20Indy=E2=86=92webvh=20migr?= =?UTF-8?q?ation=20as=20planned=20cutover,=20add=20ecosystem=20assumption?= =?UTF-8?q?=20and=20ongoing=20Indy=20credential=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrick St-Louis --- docs/deploying/IndyToWebVHMigration.md | 156 ++++++++++++++++--------- 1 file changed, 99 insertions(+), 57 deletions(-) diff --git a/docs/deploying/IndyToWebVHMigration.md b/docs/deploying/IndyToWebVHMigration.md index 659ade14aa..052ebc4a4b 100644 --- a/docs/deploying/IndyToWebVHMigration.md +++ b/docs/deploying/IndyToWebVHMigration.md @@ -1,10 +1,21 @@ -# Migrating AnonCreds Issuance from Indy to did:webvh +# Transitioning AnonCreds Issuance from Indy to did:webvh -This guide walks through the process of transitioning an ACA-Py Issuer from -publishing [AnonCreds] objects on a [Hyperledger Indy] ledger to publishing them -using the [did:webvh] method. The same ACA-Py instance can issue credentials -rooted in both methods simultaneously, so the migration can be done gradually -with no downtime or data loss. +This guide describes how to transition [AnonCreds] **issuance** from a +[Hyperledger Indy] ledger to the [did:webvh] method. It focuses on the issuance +process as a whole: issuer setup, verifier readiness, and the cutover from one +Verifiable Data Registry (VDR) to another. + +**Assumption:** All parties in your ecosystem already support did:webvh +technologically. Issuers, holders, and verifiers can resolve did:webvh +identifiers and process AnonCreds rooted in did:webvh. This guide is for when +that readiness is in place and you are moving issuance from Indy to did:webvh. + +The recommended approach is a **planned cutover**: you stop issuing from Indy +and start issuing from did:webvh on a chosen date. Existing holders keep their +Indy-rooted credentials; new issuance uses did:webvh. During and after the +cutover, the issuer must continue to **manage already-issued Indy credentials** +(e.g. revocation and other status updates) until those credentials are no longer +in use. [AnonCreds]: https://www.hyperledger.org/projects/anoncreds [Hyperledger Indy]: https://www.hyperledger.org/projects/hyperledger-indy @@ -85,7 +96,7 @@ Before starting the migration, ensure the following are in place: and verifiers can continue to verify them. There is no mechanism to "re-root" an already-issued AnonCreds credential to a different VDR. -**Must be re-created under the new DID:** +**Must be created under the new DID (new objects, same logical content):** - Schemas (same attributes, new `issuerId`) - Credential definitions (new cred def linked to the new schema) @@ -98,50 +109,65 @@ Before starting the migration, ensure the following are in place: registries simultaneously -- the correct registry is selected automatically based on the identifier pattern of the object being accessed. -## Migration Strategy: Dual-Issuance Transition +## Migration Strategy: Planned Cutover -The recommended approach is a phased transition where Indy and did:webvh -issuance run side-by-side: +The recommended approach is to **switch issuance** from Indy to did:webvh in one +planned cutover. During and after the transition, some holders have Indy-rooted +credentials (issued before the switch) and some have did:webvh-rooted +credentials (issued after). You do not issue the same credential from both +VDRs at the same time. The issuer must continue to **manage already-issued Indy +credentials** — including revocation and other status updates — until they are +fully phased out. ```mermaid flowchart LR - subgraph phase1 ["Phase 1: Indy Only"] - IndyIssue["Issue Indy credentials"] + subgraph phase1 ["Phase 1: Prepare"] + Setup["Set up did:webvh"] + Share["Share new identifiers with verifiers"] end - subgraph phase2 ["Phase 2: Dual Issuance"] - IndyIssue2["Continue Indy issuance"] - WebVHIssue["Begin did:webvh issuance"] + subgraph phase2 ["Phase 2: Verifier readiness"] + Wait["Verifiers update presentation requests"] end - subgraph phase3 ["Phase 3: did:webvh Only"] - WebVHOnly["Issue did:webvh credentials only"] + subgraph phase3 ["Phase 3: Switch to did:webvh Issuing"] + Cutover["Stop Indy issuance, issue only did:webvh"] end phase1 --> phase2 --> phase3 ``` -### Phase 1 -- Current State - -You are issuing Indy-based AnonCreds credentials. No changes yet. +### Phase 1 -- Prepare -### Phase 2 -- Dual Issuance +You are currently issuing Indy-based AnonCreds credentials. Prepare the +did:webvh side without changing issuance yet: 1. Set up the did:webvh infrastructure (server, witness, plugin) alongside your existing Indy configuration. 2. Create a did:webvh DID for your issuer. -3. Re-register your schemas and credential definitions under the new DID. -4. Start issuing new credentials using the did:webvh credential definitions - for new holders or use cases. -5. Continue issuing Indy credentials for existing holders and verifiers who - have not yet added did:webvh resolution support. -6. Communicate the transition to your verifier ecosystem so they can prepare - to resolve did:webvh identifiers. - -### Phase 3 -- did:webvh Only - -1. Once your verifier ecosystem supports did:webvh resolution, stop issuing - new Indy credentials. -2. All new issuance uses did:webvh credential definitions. -3. Previously issued Indy credentials remain valid and verifiable for their - lifetime. They do not need to be re-issued. +3. Register your schemas and credential definitions under the new DID (these + are new objects with the same logical content; only the `issuerId` and + resulting identifiers change). +4. **Share the new did:webvh identifiers with verifiers** (credential + definition IDs, schema IDs, etc.) so they can add them to their + presentation requests. During this period, the did:webvh objects exist but + are **not** used for general issuance. Limited testing (e.g. issuing a + credential or two to verify the pipeline) is acceptable. + +### Phase 2 -- Verifier readiness + +Give verifiers time to update their systems to accept credentials rooted in +either the existing Indy identifiers or the new did:webvh identifiers. Once +verifiers can resolve did:webvh and include the new credential definition IDs +in their proof requests, you can schedule the cutover. + +### Phase 3 -- Switch to did:webvh Issuing + +1. On the chosen date, **stop issuing new Indy credentials**. All new + issuance uses did:webvh credential definitions only. +2. Previously issued Indy credentials remain valid and verifiable for their + lifetime. They do not need to be re-issued. The issuer must **continue to + manage** those Indy credentials (e.g. process revocation and other status + updates) for as long as they are in use. +3. You can remove the Indy ledger connection only when all Indy credentials + have expired or been revoked and verifiers no longer need to resolve them. ## Setting Up did:webvh Issuance @@ -257,21 +283,28 @@ created in step 5. - **Same API, different identifiers** -- The `/anoncreds/*` endpoints are identical for Indy and did:webvh. The only change is the `issuerId` you supply and the format of the returned object IDs. -- **Track your credential definitions** -- During dual issuance, your - controller needs to know which cred def IDs are Indy-based and which are - did:webvh-based, so it can offer the correct credential type to each holder. +- **After cutover** -- Once you switch to did:webvh issuance, new credential + offers use did:webvh credential definition IDs. Existing Indy credentials in + holders' wallets continue to work; no re-issue is required. The controller + must continue to manage already-issued Indy credentials (e.g. revocation and + status updates) for as long as they remain in use. - **Webhook payloads** -- Webhook events for credential exchange will contain the did:webvh identifier formats. Ensure your controller can handle both - formats during the transition. + formats during and after the transition (e.g. for any Indy credentials still + in use). ## Impact on Verifiers -- **Resolution capability required** -- Verifiers must be able to resolve - did:webvh identifiers. This means they need either the `webvh` plugin loaded - or a universal resolver that supports did:webvh. -- **Accept both formats during transition** -- Proof requests should reference - both Indy and did:webvh credential definition IDs during the dual-issuance - period, or use attribute-based restrictions that are format-agnostic. +- **Readiness assumed** -- This guide assumes verifiers already support + did:webvh (resolution and AnonCreds processing). Before the issuer cuts over, + verifiers should add the new did:webvh credential definition and schema IDs + to their presentation requests so they accept credentials rooted in either + VDR during the transition. +- **Accept both formats during transition** -- After cutover, some holders will + present Indy-rooted credentials (issued before) and some did:webvh-rooted + (issued after). Proof requests should reference both Indy and did:webvh + credential definition IDs, or use attribute-based restrictions that are + format-agnostic. - **No protocol changes** -- The presentation exchange protocol (DIDComm v2, present-proof v2) is identical regardless of the underlying VDR. AnonCreds is AnonCreds -- the cryptographic operations and proof format are the same. @@ -282,26 +315,28 @@ created in step 5. Yes. Register a new schema with identical `attrNames` under your did:webvh DID. The schema content is the same; only the `issuerId` and resulting identifier -change. +change. These are new objects, not "re-registered" ones. **Do holders need to do anything?** -No. Existing Indy credentials in holders' wallets continue to work unchanged. -New credentials issued from did:webvh credential definitions are received and -stored normally -- holders do not need any special configuration. +This guide assumes holders already support did:webvh (storage and presentation of +did:webvh-rooted AnonCreds). Existing Indy credentials in holders' wallets +continue to work unchanged. New credentials issued from did:webvh credential +definitions are received and stored normally. -**Can I run Indy and did:webvh issuance simultaneously?** +**Why a planned cutover instead of issuing from both VDRs at once?** -Yes. ACA-Py's AnonCreds registry automatically routes operations to the correct -registry based on the identifier pattern (`did:indy:*` or legacy Indy format -goes to the Indy registry; `did:webvh:*` goes to the WebVH registry). Both -registries can be active at the same time with no conflicts. +Once the ecosystem supports did:webvh, the recommended path is to switch +issuance in one cutover. Issuing the same credential from both Indy and +did:webvh at the same time is not recommended for a given credential +definition; it complicates lifecycle and verifier logic. After the switch, +support Indy only for revocation of existing credentials. -**What happens to my existing Indy credentials after migration?** +**What happens to my existing Indy credentials after the switch?** They remain valid and verifiable for as long as the Indy ledger they were published on is operational. Revoking an Indy credential still works through -the Indy revocation registry. The migration only affects *new* issuance. +the Indy revocation registry. The transition only affects *new* issuance. **Do I need to keep my Indy ledger connection after Phase 3?** @@ -309,3 +344,10 @@ If you have outstanding Indy credentials that may need to be revoked, or if verifiers may still need to resolve your Indy-based credential definitions, yes. You can remove the Indy ledger connection only when all Indy credentials have expired or been revoked and verifiers no longer need to resolve them. + +**Does this model apply to other VDR or format transitions?** + +The pattern described here — prepare new identifiers, share with verifiers, +then switch issuance and phase out the old VDR — can be adapted when +transitioning between other credential formats or Verifiable Data Registries. +This document applies it specifically to Indy → did:webvh.