Resolve
Resolving a did:btcr2 identifier iteratively builds a DID document by applying BTCR2 Updates to an Initial DID Document that have been committed to the Bitcoin blockchain by Authorized Beacon Signals. The Initial DID Document is either deterministically created from the DID or provided by Sidecar Data.
DID resolution is defined by DID Resolution v0.3 [DID-RESOLUTION].
The resolve operation has the following function signature:
fn resolve(did, resolutionOptions) ->
(didResolutionMetadata, didDocument, didDocumentMetadata)
Process
Input values MUST first go through Decoding the DID and Processing Sidecar Data.
Resolution maintains the following state while building the DID document:
updates: a list of tuples, each containing Bitcoin block metadata (height, time, confirmations) and a BTCR2 Signed Update (data structure).current_document: the DID document being assembled.current_version_id: the version number being processed (starts at1).update_hash_history: a list of BTCR2 Unsigned Update hashes used to detect duplicates.block_confirmations: confirmations for the Bitcoin block that contains the most recently applied unique update.
The resolver:
- Establishes
current_documentfrom the DID or from Sidecar Data. - Repeats the following loop:
- Process Beacon Signals to populate
updatesfrom the beacon services incurrent_document. - Process
updatesArray to apply updates tocurrent_documentand refreshblock_confirmations. - Stops when processing updates returns early with a resolved
didDocumentor an error occurs.
- Process Beacon Signals to populate
The resolver returns:
didResolutionMetadata: a DID Resolution Metadata (data structure) (MAY be empty).didDocument: the final DID document (data structure).didDocumentMetadata: a DID document metadata (data structure) with REQUIRED fields:versionId:current_version_idas an ASCII string.confirmations:block_confirmationsas an integer. 1deactivated:current_document.deactivated.
Decode the DID
The did MUST be parsed with the DID-BTCR2 Identifier Decoding algorithm to retrieve version,
network, and genesis_bytes. An INVALID_DID error MUST be raised in response to any errors
raised while decoding.
Process Sidecar Data
resolutionOptions contains a sidecar property (Sidecar Data (data structure)) which SHOULD be prepared as lookup tables:
- Hash each BTCR2 Signed Update (data structure) in
sidecar.updateswith the JSON Document Hashing algorithm and build a map from hash to update (update_lookup_table). - Hash each CAS Announcement (data structure) in
sidecar.casUpdateswith the JSON Document Hashing algorithm and build a map from hash to announcement (cas_lookup_table). - Build a map from
sidecar.smtProofskeyed by proofid(smt_lookup_table).
If genesis_bytes is a SHA-256 hash, hash sidecar.genesisDocument with the JSON Document Hashing algorithm. Raise an INVALID_DID error if the computed hash does not match genesis_bytes.
Establish current_document
Resolution begins by creating an Initial Did Document called current_document (Current DID Document). The current_document is iteratively patched with BTCR2 Signed Updates announced by Authorized Beacon Signals.
Choose how to establish current_document based on the type of genesis_bytes retrieved from the decoded did:
If genesis_bytes is a SHA-256 Hash
Process the Genesis Document provided in sidecar.genesisDocument by replacing the identifier placeholder ("did:btcr2:_") with the did. A simple string replacement is sufficient. Parse the result as JSON to form current_document. The resulting DID Document (data structure) MUST be conformant to DID Core v1.1 [DID-CORE].
If genesis_bytes is a secp256k1 Public Key
Render the Initial DID Document template with these values (Bitcoin addresses MUST use the Bitcoin URI Scheme [BIP321]):
did: Thedid.public-key-multikey: Public key as a Multibase"base-58-btc"[CID] encoded string.p2pkh-bitcoin-address: Pay-to-Public-Key-Hash (P2PKH) Bitcoin address produced from the public key.p2wpkh-bitcoin-address: Pay-to-Witness-Public-Key-Hash (P2WPKH) Bitcoin address produced from the public key.p2tr-bitcoin-address: Pay-to-Taproot Bitcoin address produced from the public key.
{
"@context": [
"https://www.w3.org/TR/did-1.1",
"https://btcr2.dev/context/v1"
],
"id": "{{did}}",
"controller": [
"{{did}}"
],
"verificationMethod": [
{
"id": "{{did}}#initialKey",
"type": "Multikey",
"controller": "{{did}}",
"publicKeyMultibase": "{{public-key-multikey}}"
}
],
"authentication": [
"{{did}}#initialKey"
],
"assertionMethod": [
"{{did}}#initialKey"
],
"capabilityInvocation": [
"{{did}}#initialKey"
],
"capabilityDelegation": [
"{{did}}#initialKey"
],
"service": [
{
"id": "{{did}}#initialP2PKH",
"type": "SingletonBeacon",
"serviceEndpoint": "{{p2pkh-bitcoin-address}}"
},
{
"id": "{{did}}#initialP2WPKH",
"type": "SingletonBeacon",
"serviceEndpoint": "{{p2wpkh-bitcoin-address}}"
},
{
"id": "{{did}}#initialP2TR",
"type": "SingletonBeacon",
"serviceEndpoint": "{{p2tr-bitcoin-address}}"
}
]
}
Parse the rendered template as JSON to form current_document. The resulting DID Document (data structure) MUST be conformant to DID Core v1.1 [DID-CORE].
Process Beacon Signals
Scan the service entries in current_document (DID Document (data structure)) and identify BTCR2 Beacons by matching service type to Beacons Table 1: Beacon Types. Parse each beacon serviceEndpoint as a Beacon Address, then use those Beacon Addresses to find Bitcoin transactions whose last output script contains Signal Bytes.
Implementations are RECOMMENDED to query an indexed Bitcoin blockchain RPC service such as electrs or Esplora. Implementations MAY instead traverse blocks from the genesis block. Cache Beacon Addresses to avoid repeated transaction lookups.
For each transaction found:
- Derive
update_hashfrom the transaction’s Signal Bytes based on the beacon type:- Singleton Beacon:
update_hashis the Signal Bytes. - CAS Beacon: use Process CAS Beacon.
- SMT Beacon: use Process SMT Beacon.
- Singleton Beacon:
- Build a tuple with:
- The transaction’s block metadata (height, time, and confirmations).
- The BTCR2 Signed Update (data structure) retrieved from
update_lookup_table[update_hash].- If the update is not in
update_lookup_table, retrieve it from CAS. - Raise a
MISSING_UPDATE_DATAerror if the update is not available from either source.
- If the update is not in
- Append the tuple to
updates.
Process CAS Beacon
Treat Signal Bytes as map_update_hash. Look up map_update_hash in cas_lookup_table to retrieve a CAS Announcement (data structure) and read update_hash from the announcement entry keyed by did.
Process SMT Beacon
Treat Signal Bytes as smt_root. Look up smt_root in smt_lookup_table to retrieve an SMT Proof (data structure). Validate the proof with the SMT Proof Verification algorithm. Use smt_proof.updateId as update_hash.
Process updates Array
Return current_document as the resolved didDocument if updates is empty.
Otherwise:
- Sort
updatesby BTCR2 Signed Update (data structure)targetVersionId(ascending) with the tuple’s block height as a tiebreaker. Take the first tuple. - Set
block_confirmationsto the tuple’s block confirmations. - If
resolutionOptions.versionTimeis provided and the tuple’s block time is more recent, returncurrent_documentas the resolveddidDocument. - Set
updateto the tuple’s BTCR2 Signed Update (data structure) and checkupdate.targetVersionId. - Increment
current_version_id. - If
current_version_idis greater than or equal to the integer form ofresolutionOptions.versionId, returncurrent_document. - If
current_document.deactivatedistrue, returncurrent_document.
Check update.targetVersionId
Compare update.targetVersionId to current_version_id. Only one of three possible conditions will occur:
update.targetVersionId <= current_version_id:update.targetVersionId == current_version_id + 1:update.targetVersionId > current_version_id + 1:LATE_PUBLISHINGerror MUST be raised.
Confirm Duplicate Update
This step confirms that an update with a lower-than-expected targetVersionId is a true duplicate.
Create unsigned_update by removing the proof property from update. Hash unsigned_update with the JSON Document Hashing algorithm and compare it to update_hash_history[update.targetVersionId - 2]. Raise a LATE_PUBLISHING error if the hashes differ.
Apply update
Hash current_document with the JSON Document Hashing algorithm. Raise an INVALID_DID_UPDATE error if the result does not match the decoded update.sourceHash.
Apply the update.patch JSON Patch [RFC6902] to current_document.
Verify that current_document conforms to DID Core v1.1 [DID-CORE] and that current_document.id equals did. Otherwise raise INVALID_DID_UPDATE.
Hash the patched current_document with the JSON Document Hashing algorithm. Raise an INVALID_DID_UPDATE error if the result does not match the decoded update.targetHash.
Create unsigned_update by removing the proof property from update, hash it with the JSON Document Hashing algorithm, and append the hash to update_hash_history.
Check update.proof
Implementations MAY derive a Root Capability (data structure) from update.proof and invoke it according to Authorization Capabilities for Linked Data v0.3 [ZCAP-LD].
The resolver must locate publicKeyMultibase in current_document.verificationMethod whose id matches update.proof.verificationMethod. Otherwise raise INVALID_DID_UPDATE. Raise the same error if current_document.capabilityInvocation does not contain update.proof.verificationMethod.
Use a BIP340 Cryptosuite [BIP340-Cryptosuite] instance with publicKeyMultibase and the "bip340-jcs-2025" cryptosuite to verify update. Raise INVALID_DID_UPDATE if verification fails.
-
The number of confirmations for the Bitcoin block that contains the most recently applied unique update that yielded the resolved DID document. “Unique” refers to handling duplicated updates. When deduplicating, use the lowest block height to determine confirmations. ↩