Resolve
The Resolve operation builds a DID document. It starts with the Initial DID Document. Then it applies the BTCR2 Signed Updates that Authorized Beacon Signals announce on the Bitcoin blockchain.
The resolver keeps this state:
updates: tuples of block metadata, a Beacon Address, and a BTCR2 Signed Update.scanned_beacons: the Beacon Addresses that the resolver scanned.current_document: the Current DID Document.current_version_id: the version ofcurrent_document. The start value is1.update_hash_history: the hashes of the applied BTCR2 Unsigned Updates.block_confirmations,block_mediantimeandcurrent_block_height: the block of the last applied update.
Resolution sequence
Section titled “Resolution sequence”The sequence diagram shows the messages between the parties. The Sidecar Data is optional. If the resolver does not find a document in the Sidecar Data, it gets the document from CAS.
sequenceDiagram
autonumber
actor Controller as DID Controller
actor RP as Relying Party
participant Resolver
participant BTC as Bitcoin blockchain
participant CAS
Controller->>RP: did and Sidecar Data<br/>(optional)
RP->>Resolver: resolve(did, resolutionOptions)
Resolver->>Resolver: Decode the DID
Resolver->>Resolver: Process Sidecar Data
opt x1 DID and no Genesis Document in the Sidecar Data
Resolver->>CAS: Get the Genesis Document<br/>by genesis_bytes
CAS-->>Resolver: Genesis Document
end
Resolver->>Resolver: Establish current_document<br/>(version 1)
loop Until Process Next Update resolves didDocument
Resolver->>BTC: Find Beacon Signals of<br/>new Beacon Addresses
BTC-->>Resolver: Transactions with minConf<br/>or more confirmations
opt Document not in the Sidecar Data
Resolver->>CAS: Get the document by its hash
CAS-->>Resolver: BTCR2 Signed Update<br/>or CAS Announcement
end
Resolver->>Resolver: Process Next Update
end
Resolver-->>RP: didResolutionMetadata, didDocument,<br/>didDocumentMetadata
Resolve process
Section titled “Resolve process”Resolution is a loop of two steps: Find Beacon Signals, then Process Next Update. The loop stops when Process Next
Update resolves didDocument or raises an error.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["resolve(did, resolutionOptions)"])
Decode[["Decode the DID"]]
Sidecar[["Process Sidecar Data"]]
Options{"versionId and versionTime<br/>both set, or a value<br/>that does not parse?"}
Establish[["Establish current_document"]]
Find[["Find Beacon Signals"]]
Next[["Process Next Update"]]
Done{"didDocument<br/>resolved?"}
Return[/"Return didResolutionMetadata,<br/>didDocument, didDocumentMetadata<br/>(versionId, confirmations, deactivated,<br/>updated after an update)"/]
ErrDID(["INVALID_DID"]):::error
ErrOptions(["INVALID_OPTIONS"]):::error
Start --> Decode --> Sidecar --> Options
Decode -->|"decoding error"| ErrDID
Options -->|"yes"| ErrOptions
Options -->|"no"| Establish --> Find --> Next --> Done
Done -->|"no"| Find
Done -->|"yes"| Return
Establish current_document
Section titled “Establish current_document”Process Sidecar Data makes lookup
tables from the Sidecar Data. For an x1 DID, it also gets and checks the Genesis Document.
Establish current_document
then makes the Initial DID Document.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Process Sidecar Data, then<br/>Establish current_document"]) --> Kind{"genesis_bytes"}
Kind -->|"k: secp256k1 public key"| Render["Render the Initial DID Document template"]
Render --> KeyDoc["verificationMethod: initialKey (Multikey)<br/>service: three Singleton Beacons<br/>(P2PKH, P2WPKH, and P2TR addresses)"]
Kind -->|"x: SHA-256 hash"| InSidecar{"sidecar.genesisDocument<br/>provided?"}
InSidecar -->|"no"| FromCAS["Get the Genesis Document<br/>from CAS by genesis_bytes"]
FromCAS -->|"not found"| ErrNF(["NOT_FOUND"]):::error
FromCAS --> HashCheck
InSidecar -->|"yes"| HashCheck{"JSON Document Hash<br/>= genesis_bytes?"}
HashCheck -->|"no"| ErrDID(["INVALID_DID"]):::error
HashCheck -->|"yes"| Replace["Replace did:btcr2:_ with the did"]
KeyDoc --> Out[/"current_document<br/>(conformant to DID Core v1.1)<br/>current_version_id = 1"/]
Replace --> Out
Find Beacon Signals
Section titled “Find Beacon Signals”Find Beacon Signals scans each new
Beacon Address in current_document. An update can add a BTCR2 Beacon. The next loop then scans the new Beacon
Address. The resolver must not use unconfirmed mempool transactions.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Find Beacon Signals"]) --> Services["Read the BTCR2 Beacon services<br/>in current_document"]
Services --> NewAddr{"A Beacon Address that is<br/>not in scanned_beacons?"}
NewAddr -->|"no"| End(["Go to Process Next Update"])
NewAddr -->|"yes"| Query["Find the transactions that:<br/>• spend from the Beacon Address<br/>• have Signal Bytes in the last output<br/>• are at height ≥ current_block_height<br/>• have minConf or more confirmations (default 6)"]
Query --> Mark["Add the Beacon Address<br/>to scanned_beacons"]
Mark --> EachTx{"Next<br/>transaction?"}
EachTx -->|"no"| NewAddr
EachTx -->|"yes"| Type{"Beacon Type"}
Type -->|"Singleton Beacon"| Single["update_hash = Signal Bytes"]
Type -->|"CAS Beacon"| ProcCAS[["Process CAS Beacon"]]
Type -->|"SMT Beacon"| ProcSMT[["Process SMT Beacon"]]
ProcCAS --> Announces{"Update<br/>for the did?"}
ProcSMT --> Announces
Announces -->|"no"| EachTx
Announces -->|"yes"| Get
Single --> Get["Get the BTCR2 Signed Update from<br/>update_lookup_table, else from CAS"]
Get -->|"not found"| ErrMissing(["MISSING_UPDATE_DATA"]):::error
Get --> Check{"JSON Document Hash<br/>= update_hash?"}
Check -->|"no"| ErrSignal(["INVALID_SIGNAL_DATA"]):::error
Check -->|"yes"| Append["Append a tuple to updates:<br/>block metadata, Beacon Address,<br/>BTCR2 Signed Update"]
Append --> EachTx
Process CAS Beacon
Section titled “Process CAS Beacon”A Beacon Signal of a CAS Beacon commits to a CAS Announcement. The CAS Announcement maps each DID to the hash of its BTCR2 Signed Update.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Process CAS Beacon"]) --> Map["map_update_hash = Signal Bytes"]
Map --> Lookup["Get the CAS Announcement from<br/>cas_lookup_table, else from CAS"]
Lookup -->|"not found"| ErrMissing(["MISSING_UPDATE_DATA"]):::error
Lookup --> Entry{"Entry for<br/>the did?"}
Entry -->|"yes"| Hash[/"update_hash = the entry value"/]
Entry -->|"no"| None[/"No update for the did"/]
Process SMT Beacon
Section titled “Process SMT Beacon”A Beacon Signal of an SMT Beacon commits to the root of a Sparse Merkle Tree. The SMT Proof must come from the Sidecar Data. The resolver does not get SMT Proofs from CAS.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Process SMT Beacon"]) --> Root["smt_root = Signal Bytes"]
Root --> Lookup{"smt_lookup_table<br/>has smt_root?"}
Lookup -->|"no"| ErrMissing(["MISSING_UPDATE_DATA"]):::error
Lookup -->|"yes"| Id{"smt_proof.id<br/>= smt_root?"}
Id -->|"no"| ErrSignal(["INVALID_SIGNAL_DATA"]):::error
Id -->|"yes"| Verify[["SMT Proof Verification"]]
Verify -->|"false"| ErrSignal
Verify -->|"true"| HasId{"smt_proof<br/>has updateId?"}
HasId -->|"yes"| Hash[/"update_hash = updateId"/]
HasId -->|"no"| None[/"No update for the did"/]
Process Next Update
Section titled “Process Next Update”Process Next Update applies one
update per loop. It also selects the version that versionId or versionTime requests.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Process Next Update"]) --> AtVersion{"versionId set, and<br/>current_version_id = versionId?"}
AtVersion -->|"yes"| Resolved[/"didDocument = current_document"/]
AtVersion -->|"no"| Empty{"updates empty, or<br/>current_document.deactivated?"}
Empty -->|"yes"| HasVersion{"versionId set?"}
HasVersion -->|"yes"| ErrNF(["NOT_FOUND"]):::error
HasVersion -->|"no"| Resolved
Empty -->|"no"| Sort["Sort updates by targetVersionId,<br/>then by block height.<br/>Remove the first tuple."]
Sort --> Authorized{"current_document has a BTCR2 Beacon<br/>with the Beacon Address of the tuple?"}
Authorized -->|"no: ignore the tuple"| Loop(["Go to Find Beacon Signals"])
Authorized -->|"yes"| Time{"targetVersionId > current_version_id,<br/>versionTime set, and block<br/>mediantime after versionTime?"}
Time -->|"yes"| Resolved
Time -->|"no"| Compare{"update.targetVersionId"}
Compare -->|"≤ current_version_id"| Dup[["Confirm Duplicate Update"]]
Compare -->|"= current_version_id + 1"| Apply[["Apply update"]]
Compare -->|"> current_version_id + 1"| ErrLate(["LATE_PUBLISHING"]):::error
Dup --> Loop
Apply --> Loop
Confirm Duplicate Update
Section titled “Confirm Duplicate Update”A DID controller can announce the same update through more than one BTCR2 Beacon. The resolver accepts a duplicate only if it is the same as the update that it applied for that version.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Confirm Duplicate Update"]) --> Min{"targetVersionId < 2?"}
Min -->|"yes"| ErrUpdate(["INVALID_DID_UPDATE"]):::error
Min -->|"no"| Hash["Remove the proof.<br/>Hash the unsigned update with<br/>JSON Document Hashing."]
Hash --> Same{"Hash = update_hash_history<br/>[targetVersionId - 2]?"}
Same -->|"no"| ErrLate(["LATE_PUBLISHING"]):::error
Same -->|"yes"| Ok[/"True duplicate:<br/>no change to current_document"/]
Apply update
Section titled “Apply update”Apply update checks the update against the
two DID document hashes and the proof. Each failed check raises INVALID_DID_UPDATE.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Apply update"]) --> Source{"JSON Document Hash of<br/>current_document<br/>= update.sourceHash?"}
Source -->|"no"| Err(["INVALID_DID_UPDATE"]):::error
Source -->|"yes"| Proof[["Check update.proof"]]
Proof -->|"fail"| Err
Proof -->|"pass"| Patch["Apply update.patch (JSON Patch)<br/>to current_document"]
Patch -->|"malformed, or an operation fails"| Err
Patch --> Valid{"Conformant to DID Core v1.1,<br/>and id = did?"}
Valid -->|"no"| Err
Valid -->|"yes"| Target{"JSON Document Hash of<br/>current_document<br/>= update.targetHash?"}
Target -->|"no"| Err
Target -->|"yes"| Record["Append the unsigned update hash<br/>to update_hash_history.<br/>Set block_confirmations,<br/>block_mediantime and<br/>current_block_height.<br/>Increment current_version_id."]
Check update.proof
Section titled “Check update.proof”Check update.proof makes sure that a
capabilityInvocation key of current_document signed the update. The time checks use the block that contains the
Beacon Signal.
flowchart TD
classDef error fill:#fdecea,stroke:#b3261e,color:#b3261e
Start(["Check update.proof"]) --> Ctx{"update @context is the required array,<br/>and proof @context = update @context?"}
Ctx -->|"no"| Err(["INVALID_DID_UPDATE"]):::error
Ctx -->|"yes"| Purpose{"proofPurpose = capabilityInvocation,<br/>capabilityAction = Write,<br/>capability = urn:zcap:root:(encoded did),<br/>and invocationTarget = did?"}
Purpose -->|"no"| Err
Purpose -->|"yes"| VM{"An entry of current_document<br/>.capabilityInvocation identifies<br/>proof.verificationMethod?"}
VM -->|"no"| Err
VM -->|"yes"| Key["Get publicKeyMultibase from the<br/>embedded or referenced<br/>verification method"]
Key -->|"no verification method"| Err
Key --> Time{"If present:<br/>created ≤ block header timestamp,<br/>expires ≥ block mediantime,<br/>expires ≥ created?"}
Time -->|"no"| Err
Time -->|"yes"| Verify{"BIP340 Cryptosuite<br/>bip340-jcs-2025<br/>verifies the update?"}
Verify -->|"no"| Err
Verify -->|"yes"| Pass[/"pass"/]