Skip to content

Update and Deactivate

The Update operation changes a DID document. The DID controller makes a BTCR2 Signed Update and announces it through one or more BTCR2 Beacons of the DID document. The Deactivate operation is an Update with a fixed patch.

The update has three steps. The resolver of each relying party finds the update later through its Beacon Signal.

flowchart TD
  Start(["update(sourceDidDocument, jsonPatch,<br/>targetVersionId, verificationMethodId, signer)"])
  Version["Fresh resolution of the DID:<br/>sourceDidDocument = the DID document,<br/>targetVersionId = versionId + 1"]
  Unsigned[["Construct BTCR2 Unsigned Update"]]
  Signed[["Construct BTCR2 Signed Update"]]
  Check["Verify update.proof (SHOULD)"]
  Caught{"versionId at least the highest<br/>targetVersionId already announced?"}
  Wait["Wait until the last announced update<br/>has minConf confirmations,<br/>then resolve again"]
  Announce[["Announce DID Update"]]
  Return[/"Return signedUpdate"/]
  Keep["Keep the BTCR2 Signed Update for Sidecar Data,<br/>or publish it to CAS"]

  Version -.-> Caught
  Caught -.->|"no"| Wait -.-> Version
  Caught -.->|"yes"| Start
  Start --> Unsigned --> Signed --> Check --> Announce --> Return
  Return -.-> Keep

Use the versionId and the DID document of a fresh resolution for targetVersionId and sourceDidDocument. Do not use a local count. An announced update with an incorrect targetVersionId or an incorrect proof can make the DID unresolvable.

The DID controller MUST NOT announce an update if it cannot resolve all previous updates of the DID. That is the case when the fresh resolution returns a versionId less than the highest targetVersionId that the DID controller announced. Then the DID controller resolves the DID again after the Beacon Signal of the last announced update has resolutionOptions.minConf confirmations.

Construct BTCR2 Unsigned Update applies the patch and records the hashes of the source and target DID documents. H() is the JSON Document Hashing algorithm.

flowchart TD
  classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e

  Start(["Construct BTCR2 Unsigned Update"]) --> Patch["Apply jsonPatch to sourceDidDocument<br/>to make targetDidDocument"]
  Patch -->|"malformed, or an operation fails"| Err(["INVALID_DID_UPDATE"]):::error
  Patch --> Valid{"targetDidDocument conformant<br/>to DID Core v1.1, and<br/>id not changed?"}
  Valid -->|"no"| Err
  Valid -->|"yes"| Fill["Fill the template:<br/>@context (four context URLs)<br/>patch = jsonPatch<br/>sourceHash = H(sourceDidDocument)<br/>targetHash = H(targetDidDocument)<br/>targetVersionId"]
  Fill --> Return[/"BTCR2 Unsigned Update"/]

Construct BTCR2 Signed Update adds a Data Integrity proof. The proof invokes the root capability of the DID. The signer holds the private key or has access to it. An external signer is RECOMMENDED.

flowchart TD
  classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e

  Start(["Construct BTCR2 Signed Update"]) --> Find{"An entry of sourceDidDocument<br/>.capabilityInvocation identifies<br/>verificationMethodId?"}
  Find -->|"no"| Err(["INVALID_DID_UPDATE"]):::error
  Find -->|"yes: reference"| Lookup{"sourceDidDocument.verificationMethod<br/>has that id?"}
  Lookup -->|"no"| Err
  Lookup -->|"yes"| Suite
  Find -->|"yes: embedded object"| Suite["Create a BIP340 Cryptosuite:<br/>bip340-jcs-2025 with the signer"]
  Suite --> Config["Fill the Data Integrity Config:<br/>type DataIntegrityProof<br/>verificationMethod<br/>proofPurpose capabilityInvocation<br/>capability urn:zcap:root:(encoded did)<br/>capabilityAction Write<br/>invocationTarget = sourceDidDocument.id"]
  Config --> Proof["cryptosuite.createProof(update, proofConfig)"]
  Proof --> Return[/"BTCR2 Signed Update<br/>(the unsigned update and its proof)"/]

Announce DID Update depends on the Beacon Type. The DID controller broadcasts the Beacon Signal of a Singleton Beacon. The Aggregation Service broadcasts the Beacon Signal of an Aggregate Beacon.

flowchart TD
  Start(["Announce DID Update"]) --> Select["Select one or more BTCR2 Beacons<br/>in sourceDidDocument.service"]
  Select --> Type{"Beacon Type"}
  Type -->|"Singleton Beacon"| Hash["Signal Bytes = JSON Document Hash<br/>of the BTCR2 Signed Update"]
  Hash --> Build["Construct the Beacon Signal:<br/>spend a UTXO of the Beacon Address,<br/>last output OP_RETURN and Signal Bytes"]
  Build --> Sign["Sign with the key that<br/>controls the Beacon Address"]
  Sign --> Broadcast["Broadcast to the Bitcoin network"]
  Type -->|"CAS Beacon or SMT Beacon"| Submit["Send the update hash to the<br/>Aggregation Service"]
  Submit --> Agg[["BTCR2 Update Aggregation<br/>(see the Beacons page)"]]
  Agg --> Broadcast

A Beacon Signal is a Bitcoin transaction that spends from a Beacon Address. Its last output holds the 32 Signal Bytes. The inputs, the fee, and the change output are parameters of the non-normative funding example.

flowchart LR
  subgraph Inputs["Inputs (prevouts)"]
    direction TB
    In1["UTXO that the<br/>Beacon Address controls"]
    In2["Other UTXOs<br/>(optional)"]
  end

  Tx["Beacon Signal<br/>transaction<br/>(fee from feeRate)"]

  subgraph Outputs["Outputs"]
    direction TB
    Change["Change output<br/>(changeAddress)"]
    Last["Last output:<br/>OP_RETURN, OP_PUSH_BYTES,<br/>signal_bytes (32 bytes)"]
  end

  In1 --> Tx
  In2 --> Tx
  Tx --> Change
  Tx --> Last

A Beacon Signal is an Authorized Beacon Signal only if its Beacon Address is in the then-current DID document.

Deactivation is permanent. After the resolver applies the deactivation update, resolution stops. The resolver returns deactivated: true in the DID document metadata.

flowchart TD
  Start(["deactivate(sourceDidDocument, targetVersionId,<br/>verificationMethodId, signer)"])
  Patch["jsonPatch: add /deactivated = true"]
  Update[["Update operation"]]
  Signed[/"BTCR2 Signed Update"/]
  Resolver["The resolver applies the update:<br/>current_document.deactivated = true"]
  Stop(["Resolution stops at this version:<br/>didDocumentMetadata.deactivated = true"])

  Start --> Patch --> Update --> Signed --> Resolver --> Stop

This example shows a BTCR2 Signed Update that deactivates a DID at version 3. The values are shortened and illustrative; they don’t come from one DID.

{
"@context": [
"https://w3id.org/json-ld-patch/v1",
"https://w3id.org/zcap/v1",
"https://w3id.org/security/data-integrity/v2",
"https://btcr2.dev/context/v1"
],
"patch": [
{
"op": "add",
"path": "/deactivated",
"value": true
}
],
"sourceHash": "Rd9NR_kzIdbPBf9V5Srr5lkr3Qw6pUjr...",
"targetHash": "HDLiTin4d3Lt-Z5NSh5Kfkhqf4iglDv8...",
"targetVersionId": 3,
"proof": {
"@context": [
"https://w3id.org/json-ld-patch/v1",
"https://w3id.org/zcap/v1",
"https://w3id.org/security/data-integrity/v2",
"https://btcr2.dev/context/v1"
],
"type": "DataIntegrityProof",
"cryptosuite": "bip340-jcs-2025",
"verificationMethod": "did:btcr2:k1q5p...#initialKey",
"proofPurpose": "capabilityInvocation",
"capability": "urn:zcap:root:did%3Abtcr2%3Ak1q5p...",
"capabilityAction": "Write",
"invocationTarget": "did:btcr2:k1q5p...",
"proofValue": "z313jDDznRsbnr85HMnVFRrLYxsrbQFB..."
}
}