did:btc1 DID Method Specification

Authors:

Ryan Grant Digital Contract Design
Will Abramson Legendary Requirements
Joe Andrieu Legendary Requirements
Kevin Dean Legendary Requirements
Dan Pape Digital Contract Design
Jennie Meier Digital Contract Design

Contributors:

Kate Sills

Publication Date: 20th September 2024

Licence Statement: TODO

Abstract

did:btc1 is a censorship resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document. It improves on prior work by allowing: zero-cost off-chain DID creation; aggregated updates for scalable on-chain update costs; long-term identifiers that can support frequent updates; private communication of the DID document; private DID resolution; and non-repudiation appropriate for serious contracts.

1 Introduction and Motivation

Public digital identity was introduced to the Internet through PGP’s foundational legal work in the 1990s. Since the late 2010s, with Decentralized Identifiers, digital identity can be preserved through a rotation of key material, without relying on a centralized party. The first DID Method anchoring on the Bitcoin blockchain, did:btcr, focused on censorship resistance. However, self-sovereign identity is not complete without options for privacy as well, and the initial promise of privacy in the DID ecosystem was dubious, with heavy reliance on public DID documents.

There is a mitigation available even where some knowledge is public. Application designers can focus on mitigating correlation using “Pairwise DIDs”, which are DIDs created for every different special purpose that MAY occur. This includes not only a new DID created for every new person one starts a conversation with, but also every business that one transacts with, every group that one joins, and every online task that requires managing identity or key material.

In order to tackle reliance on public DID documents head-on, this DID Method introduces private DID Documents. However, if “private” or “pairwise” DID documents leak every time the DID is used then these DIDs do not accomplish much, either. DIDs that are shared with a relying party can be seen by not only that party but also by any third party resolver that the relying party contracts with. The next step in trust-minimization is a DID document transferred directly from the DID controller to the relying party. We call this transfer Sidecar delivery. When a relying party who is willing to cooperate with privacy concerns has the capacity to act as their own resolver, then privacy has a chance.

Lastly, many DID Methods do not anchor DID documents temporally, to create a chain-of-custody. Instead, they leave them on media that can be used to rewrite history. Bitcoin’s blockchain is the premiere global database for immutably anchoring information to a specific time. This DID Method takes care to only allow resolution to succeed when the resolver can clearly state that all data is available to present only one canonical history for a DID. This is a necessary feature when key material is used to sign serious contracts. We call this feature Non-Repudiation, and point out how an anti-feature called Late Publishing affects some other DID Methods.

did:btc1 is created for those who wish to have it all:

  • resistance to censorship;
  • non-correlation through pairwise DIDs;
  • private communication of the DID document;
  • a closed loop on private DID resolution;
  • efficiency (in cost and energy usage), via offline DID creation and aggregatable updates;
  • long-term identifiers that can support frequent updates; and
  • Non-Repudiation appropriate for serious contracts.

1.1 Comparison with Other DID Methods that Rely on Bitcoin’s Blockchain for Anchoring

1.1.1 did:btcr

BTCR is the original Bitcoin DID Method. It kept its focus on censorship resistance. It has the following limitations: * It is prohibitively expensive to maintain many DIDs, because both creation and every update require a separate on-chain transaction. * It requires storing the data for the DID document somewhere public and exposed via OP_RETURN: either at a URL, or accessible via content-addressed storage such as IPFS. * Once a DID document has been revealed as connected to a transaction, it could be possible for colluding miners to target the controlling funds for censorship, which might block updates (although this is currently highly unlikely since no valid transaction has ever been successfully censored from the blockchain by miners). * When all the prior updates were kept online, BTCR provided Non-Repudiation, however it is possible to take prior updates offline and still resolve the current BTCR update as a valid DID Document, so it cannot guarantee Non-Repudiation.

1.1.2 did:ion

ION anchors on the Bitcoin blockchain following a Sidetree approach. It has the following limitations: * Although in the normal case where data is available this DID Method performs fine, it does not fully address the Late Publishing problem, and thus attackers may manipulate edge cases to create doubt about signatures used for attestation. * It stores DID documents on IPFS, and thus does not allow keeping the DID document private between the DID controller and a relying party, even if they are capable of their own did:ion resolution.

1.1.3 did:btco

This DID Method stores the entire DID document on-chain in transactions using “inscriptions”. Because of this, its main feature of totally on-chain data is also its main structural limitation: * Those transactions are very expensive. * They cannot be kept private.

1.1.4 did:btc

This DID Method is like did:btco in that it also uses inscriptions. It adds a batching mechanism that reduces overhead but still stores all data on-chain. Its documentation lists “subject keys” as a feature, but they are just talking about defining additional keys in a DID document, which all of these DID Methods provide. In summary its main limitations are: * Creation and update require expensive transactions. * did:btc does not contemplate a way to keep DID documents private.

1.1.5 did:btc1

1.2 Features

  • There is no proprietary blockchain, only the Bitcoin blockchain.
  • Offline Creation allows creating DIDs without any on-chain transactions.
  • Aggregator Beacons can aggregate any number of updates from any number of DID controllers in one Bitcoin transaction.
  • Non-Repudiation is provided by - and Late Publishing is avoided by - ensuring 100% valid coverage of the entire update history without gaps or ambiguity.
  • Public disclosure of DID documents can be avoided by using Sidecar delivery of the necessary DID history along with the DID itself.
  • Public disclosure of updates to DID documents can also be avoided by only recording a Sparse Merkle Tree (SMT) of proofs of DID updates on-chain.
  • Resolvers need only filter transactions likely to contain updates for those DIDs of interest.
  • Any kind of key can be included in a DID Document, using an update.
  • Simple deterministic DIDs can be recovered from typical Bitcoin seed words.

1.3 Limitations

  • Resolvers require read-only view of all blocks arriving at the Bitcoin blockchain.
  • DID controllers are responsible for providing the data referenced in their Beacons’ updates (although many Beacons are expected to provide an archival service making Bundles publicly available). If this data is not available, the DID will not verify.
  • Because of the data availability responsibility, and the threat of a rogue Beacon publishing an invalid reference, the most secure Beacons will choose Bitcoin scripts that allow every DID controller a veto, although given current UTXO-sharing technology, this impedes availability.

1.4 Future Directions

  • ZCAPs delegation of the right to update only part of a DID Document;
  • More scalable Aggregator Beacons will be possible with a “transaction introspection” upgrade to Bitcoin, such as OP_CTV or OP_CAT; and
  • Beacons do not have to reuse their addresses if, in the controller’s DID document, a descriptor is used instead of an address.

2 Terminology

Beacon

A Beacon is the mechanism by which updates to DID documents are announced and discovered.

Beacons are identified by a Bitcoin address which is included as a service endpoint in a DID document along with a specific Beacon Type. By spending from a Beacon address, DID controllers announce that an update to their DID has occurred (in the case of a SingletonBeacon) or may have occurred (in the case of a CIDAggregate or SMTAggregate Beacons).

Singleton Beacon

A Singleton Beacon enables a single entity to independently post a DID Update Payload in a Beacon Signal.

Aggregate Beacon

An Aggregate Beacon enables multiple entities (possibly controlling multiple DIDs and possibly posting multiple updates) to collectively announce a set of DID Update Payloads in a Beacon Signal.

There can only ever be one DID Update Payload per did:btc1 in a Beacon Signal from an Aggregate Beacon.

Beacon Type

One of SingletonBeacon, CIDAggregateBeacon, or SMTAggregateBeacon.

Beacon Signal

Beacon Signals are Bitcoin transactions that spend from a Beacon address and include a transaction output of the format [OP_RETURN, <32_bytes>]. Beacon Signals announce one or more DID Update Payloads and provide a means for these payloads to be verified as part of the Beacon Signal.

The type of the Beacon determines how these Beacon Signals SHOULD be constructed and processed to validate a set of DID Update Payloads against the 32 bytes contained within the Beacon Signal.

Authorized Beacon Signal

An Authorized Beacon Signal is a Beacon Signal from a Beacon with a Beacon address in a then-current DID document.

DID Update Payload

A capability invocation secured using Data Integrity that invokes the root capability to update a specific did:btc1. The signed payload includes a JSON Patch object defining a set of mutations to the DID document being updated.

DID Update Bundle

A JSON object that maps did:btc1 identifiers to Content Identifiers (CIDs) that identify DID Update Payloads for the identified DID. DID Update Bundles are announced by CIDAggregator Beacons.

Merkle Tree

A tree data structure in which the leaves are a hash of a data block and every node that is not a leaf is a hash of its child node values.

The root of a Merkle tree is a single hash that is produced by recursively hashing the child nodes down to the leaves of the tree. Given the root of a Merkle tree it is possible to provide a Merkle path that proves the inclusion of some data in the tree.

Sparse Merkle Tree

A Sparse Merkle Tree (SMT) is a Merkle Tree where each data point included at the leaf of the tree is indexed.

This data structure enables proofs of both inclusion and non-inclusion of data at a given index. The instantiation in this specification has 2^256 leaves that are indexed by the SHA256 hash of a did:btc1 identifier. The data attested to at the leaves of the tree is the DID Update Payload for that did:btc1 identifier that indexed to the leaf.

Invocation

See https://w3c-ccg.github.io/zcap-spec/#terminology

Schnorr Signature

An alternative to ECDSA signatures with some major advantages, such as being able to combine digital signatures from multiple parties to form a single digital signature for the composite public key.

Bitcoin Schnorr signatures are still over the secp256k1 curve, so the same keypairs can be used to produce both Schnorr signatures and ECDSA signatures.

Taproot

Taproot is an upgrade to the Bitcoin blockchain implemented in November 2021. This upgrade enabled Bitcoin transactions to be secured using Schnorr Signatures through the introduction of a new address, a Taproot address.

Unspent Transaction Output

A Bitcoin transaction takes in transaction outputs as inputs and creates new transaction outputs potentially controlled by different addresses. An Unspent Transaction Output (UTXO) is a transaction output from a Bitcoin transaction that has not yet been included as an input, and hence spent, within another Bitcoin transaction.

Content Identifier

A Content Identifier (CID) is an identifier for some digital content (e.g., a file) generated from the content itself such that for any given content and CID generation algorithm there is a single, unique, collision-resistant identifier. This is typically done through some hashing function.

Content Addressable Storage

Content Addressable Storage (CAS) is a data storage system where content is addressable using Content Identifiers (CIDs). The Interplanetary File System (IPFS) is an example of CAS.

Non-Repudiation

Non-Repudiation is a feature of DID Methods that can clearly state that all data is available to present one canonical history for a DID.

If some data is needed but not available, the DID Method MUST NOT allow DID resolution to complete. Any changes to the history, such as may occur if a website edits a file, MUST be detected and disallowed. The Late Publishing problem breaks Non-Repudiation.

Late Publishing

Late Publishing is the ability for DID updates to be revealed at a later point in time, which alters the history of a DID document such that a state, that appeared valid before the reveal, appears after Late Publishing to never have been valid. Late Publishing breaks Non-Repudiation.

Offline Creation

Offline creation refers to when a did:btc1 identifier and corresponding initial DID document are created without requiring network interactions.

did:btc1 supports offline creation in two modes:

  • Key Pair Deterministic Creation; and
  • DID Document Initiated Creation.
Sidecar

A mechanism by which data necessary for resolving a DID is provided alongside the did:btc1 identifier being resolved, rather than being retrieved through open and standardized means (e.g., by retrieving from IPFS).

To explain the metaphor, a sidecar on a motorcycle brings along a second passenger in a transformed vehicle, the same way the DID controller MUST bring along the DID Document history to transform the situation into one that is verifiable.

Sidecar Data

Data transmitted via Sidecar.

Signal Blockheight

The blockheight of the Bitcoin block that included a specific Beacon Signal. Blockheight is used as the internal time of the resolution algorithm.

Resolution Time

A UTC timestamp of when the client makes a resolution request of the controller.

Target Time

A UTC timestamp that specifies a target time provided by a client in a resolution request to the resolver. If none is provided the target time is set to the Resolution Time.

Contemporary Blockheight

The blockheight of consideration when walking the provenance of a series of DID updates. A DID document’s contemporary time is the Signal Time of the Beacon Signal that announced the last DID Update Payload applied to the DID document.

3 Syntax

A did:btc1 DID consists of a did:btc1 prefix, followed by an OPTIONAL version number, an OPTIONAL Bitcoin network identifier, and, finally, a id-bech32 value. The id-bech32 is a Bech32 encoding of either a key-value representing a secp256k1 public key, or a hash-value of an initiating DID document. When the encoding is of a key-value the Human Readable Part (HRP) of the Bech32 encoding is set to k. When the encoding is of a hash-value the HRP is set to x. The HRP is followed by a separator which is always 1, this is then followed by the bech32-encoding.

The ABNF for a did:btc1 DID follows:

did-btc1 = "did:btc1:" [ version ":" ] [ network ":" ] id-bech32
version = 1*DIGIT
network =  "mainnet" / "signet" / "testnet" / "regtest"
id-bech32 = key-value / hash-value
hash-value = "x1" bech32-encoding
key-value = "k1" bech32-encoding
bech32-encoding = *bech32char
bech32char = "0" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "a" / "c" / 
"d" / "e" / "f" / "g" / "h" / "j" / "k" / "l" / "m" / "n" / "p" / "q" / "r" / 
"s" / "t" / "u" / "v" / "w" / "x" / "y" / "z" 

ABNF is defined by the IETF RFC5234.

3.1 Examples

All four following DIDs are equivalent:

  • did:btc1:k1q2ddta4gt5n7u6d3xwhdyua57t6awrk55ut82qvurfm0qnrxx5nw7vnsy65 - MOST COMMON
  • did:btc1:1:k1q2ddta4gt5n7u6d3xwhdyua57t6awrk55ut82qvurfm0qnrxx5nw7vnsy65
  • did:btc1:mainnet:k1q2ddta4gt5n7u6d3xwhdyua57t6awrk55ut82qvurfm0qnrxx5nw7vnsy65
  • did:btc1:1:mainnet:k1q2ddta4gt5n7u6d3xwhdyua57t6awrk55ut82qvurfm0qnrxx5nw7vnsy65

4 CRUD Operations

This section defines the Create, Read, Update, and Deactivate (CRUD) operations for the did:btc1 method.

4.1 Create

A did:btc1 identifier and associated DID document can either be created deterministically from a cryptographic seed, or it can be created from an arbitrary genesis intermediate DID document representation. In both cases, DID creation can be undertaken in an offline manner, i.e., the DID controller does not need to interact with the Bitcoin network to create their DID.

4.1.1 Deterministic Key-based Creation

For deterministic creation, the did:btc1 identifier encodes a secp256k1 public key. The key is then used to deterministically generate the initial DID document.

The algorithm takes in pubKeyBytes, a compressed SEC encoded secp256k1 public key and optional version and network values. The algorithm returns a did:btc1 identifier and corresponding initial DID document.

  1. Set genesisBytes to pubKeyBytes.
  2. Set idType to “key”.
  3. Set did to the result of did:btc1 Identifier Construction passingidType and genesisBytes and passing version and network if set.
  4. Set initialDocument to the result of passing did into the Read algorithm.
  5. Return did and initialDocument.

4.1.2 External Initial Document Creation

It is possible to create a did:btc1 from some initiating arbitrary DID document. This allows for more complex initial DID documents, including the ability to include Service Endpoints and Beacons that support aggregation.

The algorithm takes in an intermediateDocument struct, an OPTIONAL version, and an OPTIONAL network. The intermediateDocument SHOULD be a valid DID document except all places where the DID document requires the use of the identifier (e.g. the id field), this identifier SHOULD be the placeholder value did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. The DID document SHOULD include at least one verificationMethod and service of the type SingletonBeacon.

  1. Set idType to “external”.
  2. Set genesisBytes to the result of passing intermediateDocument into the JSON Canonicalization and Hash algorithm.
  3. Set did to the result of did:btc1 Identifier Construction passing idType and genesisBytes and passing version and network if set.
  4. Set initialDocument to a copy of the intermediateDocument.
  5. Replace all did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx values in the initialDocument with the did.
  6. Optionally store canonicalBytes on a Content Addressable Storage (CAS) system like IPFS. If doing so, implementations MUST use CIDs generated following the IPFS v1 algorithm.
  7. Return did and initialDocument.

4.1.3 did:btc1 Identifier Construction

A macro or convenience function can be used to construct did:btc1 identifiers. The algorithm takes two REQUIRED inputs: idType and genesisBytes, and two OPTIONAL inputs: version and network. If idType is “key”, then genesisBytes is a compressed SEC encoded secp256k1 public key. If idType is “external”, then genesisBytes is the byte representation of a SHA256 hash of a genesis intermediate DID document.

  1. Initialize result to the did:btc1 prefix string "did:btc1:".
  2. If version is not null, append version and ":" to result.
  3. If network is not null, append network and ":" to result.
  4. If idType is “key”, append the result of the Bech32 Encoding a secp256k1 Public Key algorithm, passing genesisBytes.
  5. Else if idType is “external”, append the result of the Bech32 encoding a hash-value algorithm, passing genesisBytes.
  6. Else, MUST raise an invalidDid error.
  7. Return result.

4.2 Read

The read operation is executed by a resolver after a resolution request identifying a specific did:btc1 identifier is received from a client at Resolution Time. The request MAY contain a resolutionOptions object containing additional information to be used in resolution. The resolver then attempts to resolve the DID document of the identifier at a specific Target Time. The Target Time is either provided in resolutionOptions or is set to the Resolution Time of the request.

To do so it executes the following algorithm:

  1. Let identifierComponents be the result of running the algorithm in Parse did:btc1 identifier, passing in the identifier.
  2. Set initialDocument to the result of running the algorithm in Resolve Initial Document passing in the identifier, identifierComponents and resolutionOptions.
  3. Set targetDocument to the result of running the algorithm in Resolve Target Document passing in initialDocument and resolutionOptions.
  4. Return targetDocument.

4.2.1 Parse did:btc1 Identifier

The following algorithm specifies how to parse a did:btc1 identifier according to the syntax defined in Syntax. REQUIRED input is a DID identifier. This algorithm returns an identifierComponents structure whose items are:

  • network
  • version
  • hrp
  • genesisBytes
  1. Set identifierComponents to an empty object.
  2. Using a colon (:) as the delimiter, split the identifier into an array of components.
  3. Set scheme to components[0].
  4. Set methodId to components[1].
  5. The methodId MUST be the value btc1. If this requirement fails then a methodNotSupported error MUST be raised.
  6. If the length of components equals 3, set identifierComponents.version to 1 and identifierComponents.network to mainnet. Set idBech32 to components[2].
  7. Else if length of components equals 4, check if components[2] can be cast to an integer. If so, set identifierComponents.version to components[2] and identifierComponents.network to mainnet. Otherwise, set identifierComponents.network to components[2] and identifierComponents.version to 1. Set idBech32 to components[3].
  8. Else if the length of components equals 5, set identifierComponents.version to components[2], identifierComponents.network to components[3] and idBech32 to the components[4].
  9. Else MUST raise invalidDid error. There are an incorrect number of components to the identifier.
  10. Check the validity of the identifier components. The scheme MUST be the value did. The identifierComponents.version MUST be convertible to a positive integer value. The identifierComponents.network MUST be one of mainnet, signet, testnet, or regtest. If any of these requirements fail then an invalidDid error MUST be raised.
  11. Decode idBech32 using the Bech32 algorithm to get decodeResult.
  12. Set identifierComponents.hrp to decodeResult.hrp.
  13. Set identifierComponents.genesisBytes to decodeResult.value.
  14. Return identifierComponents.

4.2.2 Resolve Initial Document

This algorithm specifies how to resolve an initial DID document and validate it against the identifier for a specific did:btc1. The algorithm takes as inputs a did:btc1 identifier, identifierComponents object and a resolutionsOptions object. This algorithm returns a valid initialDocument for that identifier.

  1. If identifierComponents.hrp value is k, then set the initialDocument to the result of running the algorithm in Deterministically Generate Initial DID Document passing in the identifier, identifierComponents values.
  2. Else If identifierComponents.hrp value is x, then set the initialDocument to the result of running External Resolution passing in the identifier, identifierComponents and resolutionOptions values.
  3. Else MUST raise invalidHRPValue error.
  4. Return initialDocument.

4.2.2.1 Deterministically Generate Initial DID Document

This algorithm deterministically generates an initial DID Document from a secp256k1 public key. It takes in a did:btc1 identifier and a identifierComponents object and returns a initialDocument.

  1. Set keyBytes to identifierComponents.genesisBytes.
  2. Initialize a initialDocument variable as an empty object.
  3. Set initialDocument.id to the identifier.
  4. Initialize a contextArray to empty array:
    1. Append the DID Core context “https://www.w3.org/ns/did/v1”.
    2. Append the Data Integrity context “https://w3id.org/security/data-integrity/v2”.
    3. Append a did:btc1 context.
    4. Set initialDocument['@context]' to contextArray.
  5. Create an initial verification method:
    1. Initialize verificationMethod to an empty object.
    2. Set verificationMethod.id to “#initialKey”.
    3. Set verificationMethod.type to “Multikey”.
    4. Set verificationMethod.controller to identifier.
    5. Set verificationMethod.publicKeyMultibase to the result of the TODO: Multikey encoding algorithm passing in keyBytes.
  6. Set initialDocument.verificationMethod to an array containing verificationMethod.
  7. Initialize a tempArray variable to an array with the single element verificationMethod.id.
  8. Set the authentication, assertionMethod, capabilityInvocation, and the capabilityDelegation properties in initialDocument to a copy of the tempArray variable.
  9. Set the initialDocument.services property in initialDocument to the result of passing the keyBytes and identifierComponents.network to the Deterministically Generate Beacon Services algorithm.
  10. Return initialDocument.
4.2.2.1.1 Deterministically Generate Beacon Services

This algorithm deterministically generates three Beacons from the single keyBytes value used to generate the deterministic did:btc1, one for each of the following three Bitcoin address types for the Bitcoin network specified by the DID: Pay-to-Public-Key-Hash (P2PKH), Pay-to-Witness-Public-Key-Hash (P2WPKH), and Pay-to-Taproot (P2TR). Spends from these three addresses can be produced only through signatures from the keyBytes’s associated private key. Each Beacon is of the type SingletonBeacon. The algorithm returns a services array.

  1. Initialize a services variable to an empty array.
  2. Set serviceId to #initialP2PKH.
  3. Set beaconAddress to the result of generating a Pay-to-Public-Key-Hash Bitcoin address from the keyBytes for the appropriate network.
  4. Set p2pkhBeacon to the result of passing serviceId, and beaconAddress to Establish Singleton Beacon.
  5. Push p2pkhBeacon to services.
  6. Set serviceId to #initialP2WPKH.
  7. Set beaconAddress to the result of generating a Pay-to-Witness-Public-Key-Hash Bitcoin address from the keyBytes for the appropriate network.
  8. Set p2wpkhBeacon to the result of passing serviceId, and beaconAddress to Establish Singleton Beacon.
  9. Push p2wpkhBeacon to services.
  10. Set serviceId to #initialP2TR.
  11. Set beaconAddress to the result of generating a Pay-to-Taproot Bitcoin address from the keyBytes for the appropriate network.
  12. Set p2trBeacon to the result of passing serviceId, and beaconAddress to Establish Singleton Beacon.
  13. Push p2trBeacon to services.
  14. Return the services array.

4.2.2.2 External Resolution

This algorithm externally retrieves an intermediateDocumentRepresentation, either by retrieving it from Content Addressable Storage (CAS) or from the Sidecar Data provided as part of the resolution request. The algorithm takes in a did:btc1 identifier, a identifierComponents object and a resolutionOptions object. It returns an initialDocument, which is a conformant DID document validated against the identifier.

  1. If resolutionOptions.sidecarData.initialDocument is not null, set initialDocument to the result of passing identifier, identifierComponents and resolutionOptions.sidecarData.initialDocument into algorithm Sidecar Initial Document Validation.
  2. Else set initialDocument to the result of passing identifier and identifierComponents to the CAS Retrieval algorithm.
  3. Validate initialDocument is a conformant DID document according to the DID Core 1.1 specification. Else MUST raise invalidDidDocument error.
  4. Return initialDocument.
4.2.2.2.1 Sidecar Initial Document Validation

This algorithm validates an initialDocument against its identifier, by first constructing the intermediateDocumentRepresentation and verifying the hash of this document matches the bytes encoded within the identifier. The algorithm takes in a did:btc1 identifier, identifierComponents and a initialDocument. The algorithm returns the initialDocument if validated, otherwise it throws an error.

  1. Set intermediateDocumentRepresentation to a copy of the initialDocument.
  2. Find and replace all values of identifier contained within the intermediateDocumentRepresentation with the string (did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx).
  3. Set hashBytes to the SHA256 hash of the intermediateDocumentRepresentation.
  4. If hashBytes does not equal identifierComponents.genesisBytes MUST throw an invalidDID error.
  5. Return initialDocument.
4.2.2.2.2 CAS Retrieval

This algorithm attempts to retrieve an initialDocument from a Content Addressable Storage (CAS) system by converting the bytes in the identifier into a Content Identifier (CID). The algorithm takes in an identifier and an identifierComponents object and returns an initialDocument.

  1. Set hashBytes to identifierComponents.genesisBytes.
  2. Set cid to the result of converting hashBytes to a IPFS v1 CID.
  3. Set intermediateDocumentRepresentation to the result of fetching the cid against a Content Addressable Storage (CAS) system such as IPFS.
  4. Set initialDocument to the copy of the intermediateDocumentRepresentation.
  5. Replace the string (did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) with the identifier throughout the initialDocument.
  6. Return initialDocument.

4.2.3 Resolve Target Document

This algorithm resolves a DID document from an initial document by walking the Bitcoin blockchain to identify Beacon Signals that announce DID Update Payloads applicable to the did:btc1 identifier being resolved.

The algorithm takes as inputs:

  • initialDocument: The DID document that was used to initiate the did:btc1 identifier being resolved as verified by the Resolve Initial Document algorithm. A DID Core conformant DID document.
  • resolutionOptions: A set of optional parameters passed in to the resolve function of the DID resolver.
  • network: The Bitcoin network of the did:btc1 identifier.

The algorithm returns targetDocument, a DID Core conformant DID document or throws an error.

  1. If resolutionOptions.versionId is not null, set targetVersionId to resolutionOptions.versionId.
  2. Else if resolutionOptions.versionTime is not null, set targetTime to resolutionOptions.versionTime.
  3. Else set targetTime to null and targetVersionId to null.
  4. Set targetBlockheight to the result of passing network and targetTime
    to the algorithm Determine Target Blockheight.
  5. Set signalsMetadata to resolutionOptions.sidecarData.signalsMetadata.
  6. Set currentVersionId to 1.
  7. If currentVersionId equals targetVersionId return initialDocument.
  8. Set updateHashHistory to an empty array.
  9. Set contemporaryBlockheight to 0.
  10. Set contemporaryDIDDocument to the initialDocument.
  11. Set targetDocument to the result of calling the Traverse Blockchain History algorithm passing in contemporaryDIDDocument, contemporaryBlockheight, currentVersionId, targetVersionId, targetBlockheight, updateHashHistory, and signalsMetadata, network.
  12. Return targetDocument.

4.2.3.1 Determine Target Blockheight

This algorithm determines the targetted Bitcoin blockheight that the resolution algorithm should traverse the blockchain history up to looking for for Beacon Signals.

This algorithm takes the following inputs:

  • network: A string identifying the Bitcoin network of the did:btc1 identifier. This algorithm MUST query the Bitcoin blockchain identified by the network.
  • targetTime: Identifies a timestamp that the DID document should be resolved to. If present, the value MUST be an ASCII string which is a valid XML datetime value.

The algorithm returns a Bitcoin blockheight.

  1. If targetTime, find the Bitcoin block on the network with greatest blockheight whose timestamp is less than the targetTime.
  2. Else find the Bitcoin block with the greatest blockheight that has at least X conformations. TODO: what is X. Is it variable?
  3. Set blockheight to block.blockheight.
  4. Return blockheight.

4.2.3.2 Traverse Blockchain History

This algorithm traverses Bitcoin blocks, starting from the block with the contemporaryBlockheight, to find beaconSignals emitted by Beacons within the contemporaryDIDDocument. Each beaconSignal is processed to retrieve a didUpdatePayload to the DID document. Each update is applied to the document and duplicates are ignored. If the algorithm reaches the block with the blockheight specified by a targetBlockheight, the contemporaryDIDDocument at that blockheight is returned assuming a single canonical history of the DID document has been constructed up to that point.

The algorithm takes the following inputs:

  • contemporaryDIDDocument: The DID document for the did:btc1 identifier being resolved that is current at the blockheight of the contemporaryBlockheight. A DID Core conformant DID document.
  • contemporaryBlockheight: A Bitcoin blockheight identifying the contemporary time at which the resolution algorithm has reached as it traverses each block in the blockchain history. An integer greater of equal to 0.
  • currentVersionId: The version of the contemporaryDIDDocument. An integer starting from 1 and incrementing by 1 with each DID Update Payload applied to the DID document.
  • targetVersionId: The version of the DID document that the resolution algorithm is attempting to resolve.
  • targetBlockheight: The Bitcoin block at which the resolution algorithm stops traversing he blockchain history. Once contemporaryBlockheight equals the targetBlockheight the algorithm with return the contemporaryDIDDocument.
  • updateHashHistory: An ordered array of SHA256 hashes of DID Update Payloads that have been applied to the DID document by the resolution algorithm in order to construct the contemporaryDIDDocument.
  • signalsMetadata: A Map from Bitcoin transaction identifiers of Beacon Signals to a struct containing Sidecar Data for that signal provided as part of the resolutionOptions. This struct contains the following properties:
    • updatePayload: A DID Update Payload which should match the update announced by the Beacon Signal. In the case of a SMT proof of non-inclusion no DID Update Payload may be provided.
    • proofs: A Sparse Merkle Tree proof that the provided updatePayload value is the value at the leaf indexed by the did:btc1 being resolved. TODO: What exactly this structure is needs to be defined.
  • network: A string identifying the Bitcoin network of the did:btc1 identifier. This algorithm MUST query the Bitcoin blockchain identified by the network.

The algorithm returns the contemporaryDIDDocument once either the targetBlockheight or targetVersionId have been reached.

  1. Set contemporaryHash to the result of passing contemporaryDIDDocument into the JSON Canonicalization and Hash algorithm.
  2. Find all beacons in contemporaryDIDDocument: All service in contemporaryDIDDocument.service where service.type equals one of SingletonBeacon, CIDAggregateBeacon and SMTAggregateBeacon Beacon.
  3. For each beacon in beacons convert the beacon.serviceEndpoint to a Bitcoin address following BIP21. Set beacon.address to the Bitcoin address.
  4. Set nextSignals to the result of calling algorithm Find Next Signals passing in beacons, contemporaryBlockheight, targetBlockheight and network.
  5. Set contemporaryBlockheight to nextSignals.contemporaryBlockheight.
  6. Set signals to nextSignals.signals.
  7. Set updates to the result of calling algorithm Process Beacon Signals passing in signals and signalsMetadata.
  8. Set orderedUpdates to the list of updates ordered by the targetVersionId property.
  9. For update in orderedUpdates:
    1. If update.targetVersionId is less than or equal to currentVersionId, run Algorithm Confirm Duplicate Update passing in update, updateHashHistory, and contemporaryHash.
    2. If update.targetVersionId equals currentVersionId + 1:
      1. Check that update.sourceHash equals contemporaryHash, else MUST raise latePublishing error.
      2. Set contemporaryDIDDocument to the result of calling Apply DID Update algorithm passing in contemporaryDIDDocument, update.
      3. Increment currentVersionId
      4. If currentVersionId equals targetVersionId return contemporaryDIDDocument.
      5. Set updateHash to the result of passing update into the JSON Canonicalization and Hash algorithm
      6. Push updateHash onto updateHashHistory.
      7. Set contemporaryHash to result of passing contemporaryDIDDocument into the JSON Canonicalization and Hash algorithm.
    3. If update.targetVersionId is greater than currentVersionId + 1, MUST throw a LatePublishing error.
  10. If contemporaryBlockheight equals targetBlockheight, return contemporaryDIDDocument
  11. Increment contemporaryBlockheight.
  12. Set targetDocument to the result of calling the Traverse Blockchain History algorithm passing in contemporaryDIDDocument, contemporaryBlockheight, currentVersionId, targetVersionId, targetBlockheight, updateHashHistory, and sidecarData.
  13. Return targetDocument.

4.2.3.3 Find Next Signals

This algorithm takes finds the next Bitcoin block containing Beacon Signals from one or more of the beacons and retuns all Beacon Signals within that block.

This algorithm takes in the following inputs:

  • contemporaryBlockhieght: The height of the block this function is looking for Beacon Signals in. An integer greater or equal to 0.
  • targetBlockheight: The height of the Bitcoin block that the resolution algorithm searches for Beacon Signals up to. An integer greater or equal to 0.
  • beacons: An array of Beacon services in the contemporary DID document. Each Beacon is a structure with the following properties:
    • id: The id of the Beacon service in the DID document. A string.
    • type: The type of the Beacon service in the DID document. A string whose values MUST be either SingletonBeacon, , CIDAggregateBeacon or SMTAggregateBeacon.
    • serviceEndpoint: A BIP21 URI representing a Bitcoin address.
    • address: The Bitcoin address decoded from the `serviceEndpoint value.
  • network: A string identifying the Bitcoin network of the did:btc1 identifier. This algorithm MUST query the Bitcoin blockchain identified by the network.

This algorithm returns a nextSignals struct, containing the following properties: - blockheight: The Bitcoin blockheight for the block containing the Beacon Signals. - signals: An array of signals. Each signal is a struct containing the following: - beaconId: The id for the Beacon that the signal was announced by. - beaconType: The type of the Beacon that announced the signal. - tx: The Bitcoin transaction that is the Beacon Signal.

  1. Set signals to an empty array.

  2. Get Bitcoin block at contemporaryBlockheight.

  3. For all txid in block.tx:

    1. Ignore the coinbase and genesis transaction identifiers. Coinbase tx identifiers are 0000000000000000000000000000000000000000000000000000000000000000 and the genesis tx identifier is 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b.
    2. Set tx to the result of fetching the Bitcoin transaction with the txid.
    3. For each tx_in in the set of transaction inpurs for tx:
      1. Set prev_tx_id to transaction identifier spent in the tx_in
      2. Ignore coinbase transaction identifiers
      3. Set prev_tx to the result of fetch the transaction with the prev_tx_id
      4. Set spent_tx_out to transaction output of prev_tx indexed by the tx_in.prev_index
      5. Set spent_address to the script pubkey address for the spent_tx_out for the provided network.
      6. If spent_address equals any of the address fields of the provided beacons array:
        1. Set beaconSignal to an object containing the following fields object:

          {
            "beaconId": "${beaconService.id}",
            "beaconType": "${beaconService.type}",
            "tx": "${tx}"
          }
        2. Push beaconSignal onto the signals array.

        3. Break the loop, transaction is a Beacon Signal, no need to check additional transaction inputs.

  4. If contemporaryBlockheight equals targetBlockheight, return a nextSignals struct:

    {
      "blockheight": "${contemporaryBlockheight}",
      "signals": "${signals}"
    }
  5. If no signals, set nextSignals to the result of algorithm Find Next Signals passing in contemporaryBlockheight + 1 and beacons.

  6. Else initialize a nextSignals object to the following:

    {
      "blockheight": "${contemporaryBlockheight}",
      "signals": "${signals}"
    }
  7. Return nextSignals.

4.2.3.4 Process Beacon Signals

This algorithm processes each Beacon Signal by attempting to retrieve and validate an announce DID Update Payload for that signal according to the type of the Beacon.

This algorithm takes as inputs:

  • beaconSignals: An array of struct representing Beacon Signals retrieved through executing the Find Next Signals algorithm. Each struct contains the follow properties:
    • beaconId: The id for the Beacon that the signal was announced by.
    • beaconType: The type of the Beacon that announced the signal.
    • tx: The Bitcoin transaction that is the Beacon Signal.
  • signalsMetadata: A Map from Bitcoin transaction identifiers of Beacon Signals to a struct containing Sidecar Data for that signal provided as part of the resolutionOptions. This struct contains the following properties:
    • updatePayload: A DID Update Payload which should match the update announced by the Beacon Signal. In the case of a SMT proof of non-inclusion no DID Update Payload may be provided.
    • proofs: A Sparse Merkle Tree proof that the provided updatePayload value is the value at the leaf indexed by the did:btc1 being resolved. TODO: What exactly this structure is needs to be defined.

The algorithm returns an array of DID Update Payloads.

  1. Set updates to an empty array.
  2. For beaconSignal in beaconSignals:
    1. Set type to beaconSignal.beaconType.
    2. Set signalTx to beaconSignal.tx.
    3. Set signalId to signalTx.id.
    4. Set signalSidecarData to signalsMetadata[signalId]. TODO: formalize structure of sidecarData
    5. Set didUpdatePayload to null.
    6. If type == SingletonBeacon:
      1. Set didUpdatePayload to the result of passing signalTx and
        signalSidecarData to the Process Singleton Beacon Signal algorithm.
    7. If type == CIDAggregateBeacon:
      1. Set didUpdatePayload to the result of passing signalTx and
        signalSidecarData to the Process CIDAggregate Beacon Signal algorithm.
    8. If type == SMTAggregateBeacon:
      1. Set didUpdatePayload to the result of passing signalTx and
        signalSidecarData to the Process SMTAggregate Beacon Signal algorithm.
    9. If didUpdatePayload is not null, push didUpdatePayload to updates.
  3. Return updates.

4.2.3.5 Confirm Duplicate Update

This algorithm takes in a DID Update Payload and verifies that the update is a duplicate against the hash history of previously applied updates. The algorithm takes in an update and an array of hashes, updateHashHistory. It throws an error if the update is not a duplicate, otherwise it returns. TODO: does this algorithm need contemporaryHash passed in?

  1. Let updateHash equal the result of passing update into the JSON Canonicalization and Hash algorithm.
  2. Let updateHashIndex equal update.targetVersionId - 2.
  3. Let historicalUpdateHash equal updateHashHistory[updateHashIndex].
  4. Assert historicalUpdateHash equals updateHash, if not MUST throw a LatePublishing error.
  5. Return

4.2.3.6 Apply DID Update

This algorithm attempts to apply a DID Update to a DID document, it first verifies the proof on the update is a valid capabilityInvocation of the root authority over the DID being resolved. Then it applies the JSON patch transformation to the DID document, checks the transformed DID document matches the targetHash specified by the update and validates it is a conformant DID document before returning it. This algorithm takes inputs contemporaryDIDDocument and an update.

  1. Set capabilityId to update.proof.capability.
  2. Set rootCapability to the result of passing capabilityId to the Dereference Root Capability Identifier algorithm.
  3. If rootCapability.invocationTarget does not equal contemporaryDIDDocument.id and rootCapability.controller does not equal contemporaryDIDDocument.id, MUST throw an invalidDidUpdate error.
  4. Instantiate a schnorr-secp256k1-2025 cryptosuite instance.
  5. Set expectedProofPurpose to capabilityInvocation.
  6. Set mediaType to ???? TODO: is this just application/json?
  7. Set documentBytes to the bytes representation of update.
  8. Set verificationResult to the result of passing mediaType, documentBytes, cryptosuite, and expectedProofPurpose into the Verify Proof algorithm defined in the VC Data Integrity specification.
  9. If verificationResult.verified equals False, MUST raise a invalidUpdateProof exception.
  10. Set targetDIDDocument to a copy of contemporaryDIDDocument.
  11. Use JSON Patch to apply the update.patch to the targetDIDDOcument.
  12. Verify that targetDIDDocument is conformant with the data model specified by the DID Core specification.
  13. Set targetHash to the result of passing targetDIDDocument to the JSON Canonicalization and Hash algorithm.
  14. Check that targetHash equals the base58 decoded update.targetHash, else raise InvalidDIDUpdate error.
  15. Return targetDIDDocument.

4.3 Update

An update to a did:btc1 document is an invoked capability using the ZCAP-LD data format, signed by a verificationMethod that has the authority to make the update as specified in the previous DID document. Capability invocations for updates MUST be authorized using Data Integrity following the schnorr-secp256k1-jcs-2025 cryptosuite with a proofPurpose of capabilityInvocation.

This algorithm takes as inputs a btc1Identifier, sourceDocument, sourceVersionId, documentPatch, a verificationMethodId, and an array of beaconIds. The sourceDocument is the DID document being updated. The documentPatch is a JSON Patch object containing a set of transformations to be applied to the sourceDocument. The result of these transformations MUST produce a DID document conformant to the DID Core specification. The verificationMethodId is an identifier for a verificationMethod within the sourceDocument. The verificationMethod identified MUST be a Schnorr secp256k1 Multikey. The beaconIds MUST identify service endpoints with one of the three Beacon Types SingletonBeacon, CIDAggregateBeacon, and SMTAggregateBeacon.

  1. Set unsignedUpdate to the result of passing btc1Identifier, sourceDocument, sourceVersionId, and documentPatch into the Construct DID Update Payload algorithm.
  2. Set verificationMethod to the result of retrieving the verificationMethod from sourceDocument using the verificationMethodId.
  3. Validate the verificationMethod is a Schnorr secp256k1 Multikey:
    1. verificationMethod.type == Multikey
    2. verificationMethod.publicKeyMultibase[4] == z66P
  4. Set didUpdateInvocation to the result of passing btc1Identifier, unsignedUpdate as didUpdatePayload,and verificationMethod to the Invoke DID Update Payload algorithm.
  5. Set signalsMetadata to the result of passing btc1Identifier, sourceDocument, beaconIds and didUpdateInvocation to the Announce DID Update algorithm.
  6. Return signalsMetadata. It is up to implementations to ensure that the signalsMetadata is persisted.

4.3.1 Construct DID Update Payload

This algorithm takes in a btc1Identifier, sourceDocument, sourceVersionId, and documentPatch objects. It applies the documentPatch to the sourceDocument and verifies the resulting targetDocument is a conformant DID document. Then it constructs and returns an unsigned DID Update Payload.

  1. Check that sourceDocument.id equals btc1Identifier else MUST raise invalidDIDUpdate error.
  2. Initialize didUpdatePayload to an empty object.
  3. Set didUpdatePayload.@context to the following list // TODO: Need to add btc1 context. ["https://w3id.org/zcap/v1", "https://w3id.org/security/data-integrity/v2", "https://w3id.org/json-ld-patch/v1"]
  4. Set didUpdatePayload.patch to documentPatch.
  5. Set targetDocument to the result of applying the documentPatch to the sourceDocument, following the JSON Patch specification.
  6. Validate targetDocument is a conformant DID document, else MUST raise invalidDIDUpdate error.
  7. Set sourceHashBytes to the result of passing sourceDocument into the JSON Canonicalization and Hash algorithm.
  8. Set didUpdatePayload.sourceHash to the base58-btc Multibase encoding of sourceHashBytes. // Question: is base58btc the correct encoding scheme?
  9. Set targetHashBytes to the result of passing targetDocument into the JSON Canonicalization and Hash algorithm.
  10. Set didUpdatePayload.targetHash to the base58-btc Multibase encoding of targetHashBytes.
  11. Set didUpdatePayload.targetVersionId to sourceVersionId + 1
  12. Return didUpdatePayload.

4.3.2 Invoke DID Update Payload

This algorithm takes in a btc1Identifier, an unsigned didUpdatePayload, and a verificationMethod. The algorithm retrieves the privateKeyBytes for the verificationMethod and adds a capability invocation in the form of a Data Integrity proof following the Authorization Capabilities (ZCAP-LD) and VC Data Integrity specifications.

The algorithm returns the invoked DID Update Payload.

  1. Set privateKeyBytes to the result of retrieving the private key bytes associated with the verificationMethod value. How this is achieved is left to the implementation.
  2. Set rootCapability to the result of passing btc1Identifier into the Derive Root Capability from did:btc1 Identifier algorithm.
  3. Initialize proofOptions to an empty object.
  4. Set proofOptions.type to DataIntegrityProof.
  5. Set proofOptions.cryptosuite to schnorr-secp256k1-jcs-2025.
  6. Set proofOptions.verificationMethod to verificationMethod.id.
  7. Set proofOptions.proofPurpose to capabilityInvocation.
  8. Set proofOptions.capability to rootCapability.id.
  9. Set proofOptions.capabilityAction to Write. // Wonder if we actually need this. Aren’t we always writing.
  10. Set cryptosuite to the result of executing the Cryptosuite Instantiation algorithm from the Schnorr secp256k1 Data Integrity specification passing in proofOptions.
  11. // TODO: need to set up the proof instantiation such that it can resolve / dereference the root capability. This is deterministic from the DID.
  12. Set didUpdateInvocation to the result of executing the Add Proof algorithm from VC Data Integrity passing didUpdatePayload as the input document, cryptosuite, and the set of proofOptions.
  13. Return didUpdateInvocation.

4.3.3 Announce DID Update

This algorithm takes in a btc1Identifier, sourceDocument, an array of beaconIds, and a didUpdateInvocation. It retrieves beaconServices from the sourceDocument and calls the Broadcast DID Update algorithm corresponding to the type of the Beacon. The algorithm returns an array of signalsMetadata, containing the necessary data to validate the Beacon Signal against the didUpdateInvocation.

  1. Set beaconServices to an empty array.

  2. Set signalMetadata to an empty array.

  3. For beaconId in beaconIds:

    1. Find beaconService in sourceDocument.service with an id property equal to beaconId.
    2. If no beaconService MUST throw beaconNotFound error.
    3. Push beaconService to beaconServices.
  4. For beaconService in beaconServices:

    1. Set signalMetadata to null.
    2. If beaconService.type == SingletonBeacon:
      1. Set signalMetadata to the result of passing beaconService and didUpdateInvocation to the Broadcast Singleton Beacon Signal algorithm.
    3. Else If beaconService.type == CIDAggregateBeacon:
      1. Set signalMetadata to the result of passing btc1Identifier, beaconService and didUpdateInvocation to the Broadcast CIDAggregate Beacon Signal algorithm.
    4. Else If beaconService.type == SMTAggregateBeacon:
      1. Set signalMetadata to the result of passing btc1Identifier, beaconService and didUpdateInvocation to the Broadcast SMTAggregate Beacon Signal algorithm.
    5. Else:
      1. MUST throw invalidBeacon error.
  5. Merge signalMetadata into signalsMetadata.

  6. Return signalsMetadata.

4.4 Deactivate

To deactivate a did:btc1, the DID controller MUST add the property deactivated with the value true on the DID document. To do this, the DID controller constructs a valid DID Update Payload with a JSON patch that adds this property and announces the payload through a Beacon in their current DID document following the algorithm in Update. Once a did:btc1 has been deactivated this state is considered permanent and resolution MUST terminate.

5 Update Beacons

Beacons are the mechanism by which a DID controller announces an update to their DID document by broadcasting an attestation to this update onto the public Bitcoin network. Beacons are identified by a Bitcoin address and emit Beacon Signals by broadcasting a valid Bitcoin transaction that spends from this Beacon address. These transactions include attestations to a set of didUpdatePayloads, either in the form of Content Identifiers (CIDs) or Sparse Merkle Tree (SMT) roots. Beacons are included as a service in DID documents, with the Service Endpoint identifying a Bitcoin address to watch for Beacon Signals. All Beacon Signals broadcast from this Beacon MUST be processed as part of resolution (see Read). The type of the Beacon service in the DID document defines how Beacon Signals SHOULD be processed.

did:btc1 supports different Beacon Types, with each type defining a set of algorithms for:

  1. How a Beacon can be established and added as a service to a DID document.
  2. How attestations to DID updates are broadcast within Beacon Signals.
  3. How a resolver processes a Beacon Signal, identifying, verifying, and applying the authorized mutations to a DID document for a specific DID.

This is an extendable mechanism, such that in the future new Beacon Types could be added. It would be up to the resolver to determine if the Beacon Type is a mechanism they support and are willing to trust. If they are unable to support a Beacon Type and a DID they are resolving uses that type then the DID MUST be treated as invalid.

The current, active Beacons of a DID document are specified in the document’s service property. By updating the DID document, a DID controller can change the set of Beacons they can use to broadcast updates to their DID document over time. Resolution of a DID MUST process signals from all Beacons identified in the latest DID document and apply them in order determined by the version specified by the didUpdatePayload.

All resolvers of did:btc1 DIDs MUST support the core Beacon Types defined in this specification.

5.1 Singleton Beacon

5.1.1 Establish Singleton Beacon

A Singleton Beacon is a Beacon that can be used to publish a single DID Update Payload targeting a single DID document. The serviceEndpoint for this Beacon Type is a Bitcoin address represented as a URI following the BIP21 scheme. It is RECOMMENDED that this Bitcoin address be under the sole control of the DID controller. How the Bitcoin address and the cryptographic material that controls it are generated is left to the implementation.

This algorithm takes in a Bitcoin address and a serviceId and returns a Singleton Beacon service.

  1. Initialize a service variable to an empty object.
  2. Set service.id to serviceId.
  3. Set service.type to “SingletonBeacon”.
  4. Set service.serviceEndpoint to the result of converting address to a URI as per BIP21
  5. Return service.

// TODO: Style and link to examples.

{
   "id": "#singletonBeacon", 
   "type": "SingletonBeacon", 
   "serviceEndpoint": "${beaconUri}",
}

5.1.2 Broadcast Singleton Beacon Signal

This algorithm is called by the Announce DID Update algorithm as part of the Update operation, if the Beacon being used is of the type SingletonBeacon. It takes as input a Beacon service and a secured didUpdatePayload. The algorithm constructs a Bitcoin transaction that spends from the Beacon address identified in the service and contains a transaction output of the format [OP_RETURN, OP_PUSH32, <hashBytes>], where hashBytes is the SHA256 hash of the canonical didUpdatePayload. The Bitcoin transaction is then signed and broadcast to the Bitcoin network, thereby publicly announcing a DID update in a Beacon Signal.

The algorithm returns a signalMetadata object mapping the Bitcoin transaction identifier of the Beacon Signal to the necessary data needed to verify the signal announces a specific DID Update Payload.

  1. Initialize an addressURI variable to beacon.serviceEndpoint.
  2. Set bitcoinAddress to the decoding of addressURI following BIP21.
  3. Ensure bitcoinAddress is funded, if not, fund this address.
  4. Set hashBytes to the result of passing didUpdatePayload to the JSON Canonicalization and Hash algorithm.
  5. Initialize spendTx to a Bitcoin transaction that spends a transaction controlled by the bitcoinAddress and contains at least one transaction output. This output MUST have the following format [OP_RETURN, OP_PUSH32, hashBytes]
  6. Retrieve the cryptographic material, e.g private key or signing capability, associated with the bitcoinAddress or service. How this is done is left to the implementer.
  7. Sign the spendTx.
  8. Broadcast spendTx to the Bitcoin network.
  9. Set signalId to the Bitcoin transaction identifier of spendTx.
  10. Initialize signalMetadata to an empty object.
  11. Set signalMetadata.updatePayload to didUpdatePayload.
  12. Return the object {<signalId>: signalMetadata}.

5.1.3 Process Singleton Beacon Signal

This algorithm is called by the Process Beacon Signals algorithm as part of the Read operation. It takes as inputs a Bitcoin transaction, tx, representing a Beacon Signal and a optional object, signalSidecarData, containing any sidecar data provided to the resolver for the Beacon Signal identified by the Bitcoin transaction identifier.

The algorithm returns the DID Update payload announced by the Beacon Signal or throws an error.

  1. Initialize a txOut variable to the 0th transaction output of the tx.
  2. Set didUpdatePayload to null.
  3. Check txOut is of the format [OP_RETURN, OP_PUSH32, <32bytes>], if not, then return didUpdatePayload. The Bitcoin transaction is not a Beacon Signal.
  4. Set hashBytes to the 32 bytes in the txOut.
  5. If signalSidecarData:
    1. Set didUpdatePayload to signalSidecarData.updatePayload
    2. Set updateHashBytes to the result of passing didUpdatePayload to the JSON Canonicalization and Hash algorithm.
    3. If updateHashBytes does not equal hashBytes, MUST throw an invalidSidecarData error.
    4. Return didUpdatePayload
  6. Else:
    1. Set didUpdatePayload to the result of passing hashBytes into the Fetch Content from Addressable Storage algorithm.
    2. If didUpdatePayload is null, MUST raise a latePublishingError. MAY identify Beacon Signal to resolver and request additional Sidecar data be provided.
  7. Return didUpdatePayload.

5.2 CIDAggregate Beacon

A Beacon of the type CIDAggregatorBeacon is a Beacon that publishes Bitcoin transactions containing a Content Identifier (CID) announcing an Aggregated DID Update Bundle. An Aggregated DID Update Bundle is a JSON object that maps did:btc1 identifiers to CID values for the individual DID Update Payloads. The Aggregated DID Update Bundle CID (bundleCID) SHOULD be resolvable against a Content Addressable Storage (CAS) system such as IPFS, while the CID for the DID Update Payload (payloadCID) MAY be resolvable against a CAS or provided through a Sidecar mechanism. It is RECOMMENDED that this type of Beacon is only included in a DID document if the DID controller is REQUIRED to participate in authorizing Bitcoin transactions from this Beacon. In other words, this Beacon SHOULD identify an n-of-n P2TR Bitcoin address where n is the number of unique DID controllers submitting updates through the Beacon.

5.2.1 Establish CIDAggregate Beacon

To establish a CIDAggregatorBeacon, a cohort of cooperating parties SHOULD generate an n-of-n P2TR Bitcoin address where each party contributes a public key. Furthermore, each party SHOULD verify that their key is part of the address and all other keys that are part of the address are keys with controllers able to produce valid signatures.

To establish a Beacon there are two roles. One is the cohort participant, they want to join a Beacon cohort and submit a request to do so with a key and proof of control over that key. The other is the Beacon coordinator, they advertise and curate Beacon cohorts by combining Beacon participants into cohorts, verifying proofs of control, and producing Beacon addresses.

5.2.1.1 Create CIDAggregate Beacon Advertisement

Any entity MAY act in the role of Beacon coordinator, creating a Beacon advertisement that they can broadcast across any medium. A Beacon advertisement specifies the properties of the Beacon that the coordinator intends to establish, including the Beacon Type, cohort size, update frequency, and response latency. Once the advertisement has been created and broadcast, the coordinator waits for enough participants to opt in before establishing the Beacon.

5.2.1.2 CIDAggregate Beacon Opt-in

DID controllers who wish to participate in a Beacon cohort first find potential Beacon advertisements that meet their needs. This includes checking the Beacon terms and update frequency, etc. If satisfied, they create a secp256k1 cohort keypair and send an Opt-In request to the endpoint specified in the advertisement.

5.2.1.3 Cohort Set

Once a Beacon Aggregator has received enough opt-in responses from participants to satisfy the Beacon properties, they generate the n-of-n P2TR Bitcoin address for the Beacon. The address and all the cohort public keys the address is constructed from are then sent to all participants in a CohortSet message.

5.2.1.4 Add Beacon Service Endpoint to DID Document

A participant receiving a CohortSet message first verifies their cohort key is included in the cohort, then calculates the P2TR Beacon address for themselves and verifies it matches the address provided. They MAY wait until the Beacon address is funded before adding the Beacon as a service in the DID document. The following is an example of the Beacon service endpoint the DID controller adds into their DID document, the Beacon address is converted into a URI following BIP21:

{
  "id": "#cidAggregateBeacon",
  "type": "CIDAggregateBeacon",
  "serviceEndpoint": "bitcoin:tb1pfdnyc8vxeca2zpsg365sn308dmrpka4e0n9c5axmp2nptdf7j6ts7eqhr8"
}

5.2.2 Broadcast CIDAggregate Beacon Signal

This is an algorithm involving two roles: a set of cohort participants and a Beacon coordinator. The Beacon coordinator collects individual DID Update Payload Content Identifiers (CIDs) for specific did:btc1s and aggregates them into a DID Update Bundle, which is then published to a Content Addressable Storage (CAS). The CID for the DID Update Bundle is included in a Partially Signed Bitcoin Transaction (PSBT) transaction output spent from the Beacon’s n-of-n address. Each of the n cohort participants in the Beacon MUST sign the transaction before it can be broadcast to the network. It is RECOMMENDED that cohort participants keep a copy of the DID Update Bundle and separately pin it to the CAS.

5.2.2.1 Submit DID Update

A cohort participant submits a CID for a DID Update Payload along with the DID the update is for to the Beacon coordinator for a Beacon identified in their DID document.

5.2.2.2 Aggregate DID Updates

A set of DID updates are aggregated together to create an update bundle. This bundle is published to the CAS (e.g. IPFS) and the CID for the bundle is included in a Partially Signed Bitcoin Transaction (PSBT). This PSBT is the broadcast to all Beacon cohort participants for authorization.

5.2.2.3 Authorize Beacon Signal

On receiving an Authorized Beacon Signal request, DID controllers MUST verify that the DID Update Bundle either includes the CID for the DID Update Payload they submitted, or includes no entry for their DID. Once satisfied, the DID controller signs the PSBT following the MuSig2 protocol using the key they generated when establishing the Beacon.

5.2.2.4 Broadcast Beacon Signal

Once all Beacon cohort participants have authorized the Beacon Signal by signing the PSBT, a valid, spendable Bitcoin transaction can be created by aggregating the signatures following Schnorr. This Bitcoin transaction can then be broadcast to the network.

5.2.3 Process CIDAggregate Beacon Signal

A Beacon Signal from a CIDAggregate Beacon is a Bitcoin transaction that contains the hashBytes of a DID Update Bundle in its first transaction output. The corresponding DID Update Bundle MUST either be provided through Sidecar Data or by converting hashBytes into a IPFS v1 Content Identifier and attempting to retrieve it from Content Addressable Storage. The DID Update Bundle maps from did:btc1 identifiers to hashes of DID Update payloads applicable for that identifier. Again this algorithm attempts to retrieve and validate the DID Update Payload identified for the identifier being resolved. If successful, the DID Update Payload is returned.

This algorithm is called by the Process Beacon Signals algorithm as part of the Read operation. It takes as inputs a did:btc1 identifier, btc1Identifier, a Beacon Signal, tx, and a optional object, signalSidecarData, containing any sidecar data provided to the resolver for the Beacon Signal identified by the Bitcoin transaction identifier.

The algorithm returns the DID Update payload announced by the Beacon Signal for the did:btc1 identifier being resolved or throws an error.

  1. Initialize a txOut variable to the 0th transaction output of the tx.
  2. Set didUpdatePayload to null.
  3. Check txOut is of the format [OP_RETURN, OP_PUSH32, 32BYTE], if not, then return didUpdatePayload. The Bitcoin transaction is not a Beacon Signal.
  4. Set hashBytes to the 32 bytes in the txOut.
  5. If signalSidecarData:
    1. Set didUpdateBundle to signalSidecarData.updateBundle
    2. Set bundleHashBytes to the result of passing didUpdateBundle to the JSON Canonicalization and Hash algorithm.
    3. If bundleHashBytes does not equal hashBytes, MUST raise an invalidSidecarData error. MAY identify Beacon Signal to resolver and request additional Sidecar data be provided.
    4. Set signalUpdateHashBytes to didUpdateBundle.get(btc1Identifier)
    5. If signalUpdateHashBytes is null, MUST raise an incompleteSidecarData error. MAY identify Beacon Signal to resolver and request additional Sidecar data be provided.
    6. Set didUpdatePayload to signalSidecarData.updatePayload.
    7. Set updateHashBytes to the result of passing didUpdatePayload to the JSON Canonicalization and Hash algorithm.
    8. If signalUpdateHashBytes does not equal updateHashBytes, MUST raise an invalidSidecarData error. MAY identify Beacon Signal to resolver and request additional Sidecar data be provided.
  6. Else:
    1. Set didUpdateBundle to the result of calling the [Fetch From Content Addressable Storage] algorithm passing in hashBytes.
    2. If didUpdateBundle is null, MUST raise a latePublishingError. MAY identify Beacon Signal to resolver and request additional Sidecar data be provided.
    3. Set signalUpdateHashBytes to the didUpdateBundle.get(btc1Identifier) // TODO: Will need to decode this. Bundle is not going to store raw bytes
    4. Set didUpdatePayload to the result of calling the [Fetch From Content Addressable Storage] algorithm passing in signalUpdateHashBytes.
    5. If didUpdatePayload is null, MUST raise a latePublishingError. MAY identify Beacon Signal to resolver and request additional Sidecar data be provided.
  7. Return didUpdatePayload

5.3 SMTAggregate Beacon

A SMTAggregate Beacon is a Beacon whose Beacon Signals are Bitcoin transactions containing the root of a Sparse Merkle Tree (SMT). The SMT root attests to a set of DID Update Payloads, however, the updates themselves MUST be provided along with a proof of inclusion against the SMT root through a Sidecar mechanism during resolution. Using the SMT root a resolver can then verify the inclusion proof for the given DID Update Payload. If a DID document includes a SMTAggregator Beacon in their set of Beacon services, then they MUST provide proofs for each signal that the Beacon broadcasts. If they did not submit an update to their DID in a signal, then they MUST provide a proof of non-inclusion for that signal.

5.3.1 Establish Beacon

This algorithm is essentially the same as for the CIDAggregate Beacon in Establish CIDAggregate Beacon. A cohort of DID controllers need to coordinate to produce a Bitcoin address that will act as the Beacon. It is RECOMMENDED this is an n-of-n P2TR address, with n being the set of DID controllers in the cohort. Once the address has been created, and all parties in the cohort acknowledge their intention to participate in that Beacon, each DID controller SHOULD add the Beacon as a service to their DID document.

Additionally, the SMTAggregate Beacon cohort participants MUST register the did:btc1 identifiers they intend use this Beacon with. This is so the Beacon coordinator can generate the necessary proofs of both inclusion and non-inclusion for each DID.

5.3.2 Broadcast SMTAggregate Beacon Signal

To publish a DID Update Payload, the DID controller MUST get a hash of the DID Update Payload included at the leaf of the Sparse Merkle Tree (SMT) identified by their did:btc1 identifier and receive an inclusion proof for this data. If a member of the Beacon cohort does not wish to announce an update in a Beacon Signal, they MUST receive and verify a proof of non-inclusion for their DID. Upon verifying the non-inclusion proof against the SMT root contained in the Beacon Signal, they SHOULD accept and authorize the signal following the MuSig2 protocol. Once all members of the cohort have authorized the signal, it can be broadcast as a transaction to the Bitcoin network. DID controllers are responsible for persisting their DID updates and proofs, these will need to be provided through a Sidecar mechanism during a resolution process.

5.3.3 Process SMTAggregate Beacon Signal

A Beacon Signal from a SMTAggregate Beacon is a Bitcoin transaction with the first transaction output of the format [OP_RETURN, OP_PUSH32, <32bytes>]. The 32 bytes of data contained within this transaction output represent the root of a Sparse Merkle Tree (SMT). This SMT aggregates a set of hashes of DID Update payloads. In order to process these Beacon Signals, the resolver MUST have been passed Sidecar data for this signal containing either the DID Update payload object and a SMT proof that the hash of this object is in the SMT at the leaf indexed by the did:btc1identifier being resolved. Or theSidecar data:: MUST contain a proof that the leaf indexed by the did:btc1identifier is empty, thereby proving that theSMT:: does not contain an update for their identifier.

This algorithm is called by the Process Beacon Signals algorithm as part of the Read operation. It takes as inputs a did:btc1 identifier, btc1Identifier, a Beacon Signal, tx, and a optional object, signalSidecarData, containing any sidecar data provided to the resolver for the Beacon Signal identified by the Bitcoin transaction identifier.

The algorithm returns the DID Update payload announced by the Beacon Signal for the did:btc1 identifier being resolved or throws an error.

  1. Initialize a txOut variable to the 0th transaction output of the tx.
  2. Check txOut is of the format [OP_RETURN, OP_PUSH32, <32byte>], if not, then return null. The Bitcoin transaction is not a Beacon Signal.
  3. If no signalSidecarData, MUST raise an incompleteSidecarData error. MAY identify the Beacon Signal to resolver and request additional Sidecar data be provided.
  4. Set smtProof to signalSidecarData.smtProof.
  5. If no smtProof, MUST raise a latePublishing error.
  6. Set smtRoot to the 32 bytes of data in txOut.
  7. Set didUpdatePayload to signalSidecarData.updatePayload.
  8. Set updateHashBytes to the result of passing didUpdatePayload to the JSON Canonicalization and Hash algorithm.
  9. Set identifierBytes to the result of converting btc1Identifier to bytes.
  10. Verify the SMT proof against the smtRoot with the key as identifierBytes and the value `updateHashBytes. // TODO: Need to define algorithm(s) for SMT properly.
  11. Return didUpdatePayload

6 Security Considerations

6.1 did:btc1 Design Considerations

6.1.1 Late Publishing

did:btc1 was designed to avoid Late Publishing such that, independent of when a resolution occurs, the DID document history and provenance is guaranteed to be invariant. This is achieved through requiring strict ordering of DID updates and complete coverage of all relevant Beacon Signals. Resolvers MUST process all relevant Beacon Signals and enforce strict ordering.

6.1.2 Invalidation Attacks

Invalidation attacks are where adversaries are able to publish Beacon Signals that claim to contain updates for DIDs they do not control. Due to the requirement for complete coverage, if these updates can not be retrieved by a resolver, the DID MUST be considered invalid. To prevent these attacks, all Beacon Signals SHOULD be authorized by all cohort participants using an n-of-n multi-signature. That way DID controllers can verify the updates announced within a Beacon Signal before authorizing it.

6.2 Considerations Deploying did:btc1

6.2.1 Data Retention

did:btc1 requires resolvers to have complete coverage of all relevant Beacon Signals and the associated updates for a specific did:btc1 to prevent Late Publishing. This means that the updates MUST be available to resolver at the time of resolution. It is the responsibility of DID controllers to persist this data, otherwise the consequence is that the DID MAY not be resolvable (depending on data accessibility from the perspective of the resolver). DID controllers MAY store DID Update Payloads on a Content Addressable Storage (CAS) system. DID controllers SHOULD consider that in some constrained environments it is preferable to discard a DID and replace it with a newly issued DID, rather than rotating a key.

6.2.2 Aggregator Beacon Address Verification

An Aggregator Beacon Address SHOULD be an n-of-n Pay-to-Taproot (P2TR) address, with a cohort key contributed to the n by each of the cohort participants. DID controllers participating in aggregation cohorts SHOULD verify the Beacon address is an n-of-n and that one of the n keys is the cohort key provided to the Beacon coordinator. This can be achieved only by constructing the address for themselves from the set of cohort keys which the coordinator SHOULD provide.

6.2.3 Aggregator Beacon Signal Verification

Beacon Signals from Aggregators that a DID controller is a participant of will either announce an update for their DID or will contain no update for their DID. The DID controller SHOULD verify that the Beacon Signal announces the update they expect (or no update) for all Beacon Signals broadcast by Aggregators before authorizing them. If they do not, then invalidation attacks become possible where a Beacon Signal announces an update for a DID that cannot be retrieved, causing the DID to be invalidated.

6.2.4 Key Compromise

In did:btc1, cryptographic keys authorize both DID updates and Beacon Signals. Should these keys get compromised without the DID controller’s knowledge, it would be possible for an adversary to take control of a DID by submitting a DID Update Payload to a Beacon that replaces key material and Beacons in the DID document for ones under the adversary’s control. Such an attack would be detectable by the DID controller, as they would see a valid spend from a Beacon that they did not authorize. Additionally, if the DID relied on Sidecar data, without access to this data the DID would be useless to the adversary as they would be unable to demonstrate a valid complete history of the DID during resolution.

6.2.5 Cryptographic Compromise

The security of did:btc1 identifiers depends on the security of Schnorr Signatures over the secp256k1 curve. It is this signature scheme that is used to secure both the Beacon Signals and DID Update Payloads. Should vulnerabilities be discovered in this scheme or if advancements in quantum computing compromise its cryptographic foundations, the did:btc1 method would become obsolete.

6.2.6 Bitcoin Blockchain Compromise

The security of did:btc1 identifiers depends on the security of the Bitcoin blockchain. Should the Bitcoin blockchain become compromised such that its history could be rewritten, for example through a 51% attack, then Beacon Signals that were once part of the blockchain could be removed or replaced–although the longer these Signals have been included in the blockchain the more difficult this becomes. A 51% attack could prevent future Beacon Signals from being included within the network, however this would require the 51% attack to remain indefinitely enforced. Furthermore, without Key Compromise related to a specific DID, the compromise of the Bitcoin blockchain would not enable adversarial parties to take control of a DID.

7 Privacy Considerations

7.1 did:btc1 Design Considerations

7.1.1 Updates Need not be Public

did:btc1 was designed such that updates to DID documents are NOT REQUIRED to be public. Bitcoin is used to publicly announce and anchor updates to DID documents, however the updates themselves can be kept private by DID controllers and provided through a Sidecar mechanism at Resolution Time.

7.1.2 DID Documents Need not be Public

Since updates to DID documents are NOT REQUIRED to be public, neither are did:btc1 DID documents. A did:btc1 DID document is an initial document plus a series of updates to that DID document. To keep the DID document fully private, the DID controller can choose to use an externally resolved initial did:btc1 and not place the initial DID document on a Content Addressable Storage (CAS) system such as IPFS. The initial DID document can be provided at Resolution Time through a Sidecar mechanism along with the collection of updates that can be verified against the relevant Beacon Signals for the DID being resolved.

7.1.3 Offline DID Generation

did:btc1 was designed to support offline DID generation, that is, the creation of a did:btc1 identifier and associated DID document without any network calls to the Bitcoin blockchain. This is possible in both deterministic and externally resolvable DIDs.

7.1.4 Beacon Coordinators do not Need to View or Validate DID Documents or Document Updates

Beacon coordinators in did:btc1 are entities that coordinate Aggregator Beacons and the corresponding Beacon Signals that announce and anchor an aggregated set of DID Update Payloads. However, in did:btc1, Aggregators are able to coordinate Beacon Signals without needing to view or validate DID documents or the updates. Instead, they are provided with a hash or CID of the update for a specific DID which they include in the Beacon Signal according to the type of the Beacon.

7.1.5 Consensus Splits in Implementation can Destroy Non-Repudiation

Because Non-Repudiation requires following a complete stream of updates to a DID, any consensus forks in which DID updates to apply can be used to permanently diverge the history for a DID, and thus the key material, creating alternate attestation histories. As a concrete example, a seemingly small difference between two clients in interpretation of the specification could be used fraudulently to sign a loan in one history and claim that it was never signed in another history.

In order to prevent consensus splits, did:btc1 needs a particularly good test suite. It MUST be designed to challenge all the foreseeable edge cases as well as maintained to find new ones.

7.2 Considerations Deploying did:btc1

7.2.1 Update Payloads Stored in Content Addressable Storage (CAS) Systems are Publicly Accessible

Update payloads stored in Content Addressable Storage (CAS) such as IPFS SHOULD be considered public. Anyone MAY retrieve this information (assuming they have the CID) and use it to track the DID over time. IPFS node operators would have access to all content stored on IPFS and could choose to index certain data like DID Update Payloads, including the updates posted by that DID’s Beacon. This MAY advertise information about the DID that the controller wishes to remain private.

Those parties most concerned about privacy SHOULD maintain their DID Update Payloads in a Sidecar manner and provide them to necessary parties during resolution. It is RECOMMENDED not to include any sensitive data other than the necessary DID update data.

7.2.2 Beacon Coordinators Know the DIDs Being Aggregated by a Cohort

Within Sparse Merkle Tree (SMT) Beacons, the DID is used as a path to a leaf node in the SMT. The coordinator MUST know these paths for them to be able to construct the tree and generate the correct proof paths. Within Content Identifier (CID) based Beacons, the coordinator MUST construct an aggregated bundle that includes all DIDs aggregated as a key to the CID for that DID’s Update Payload. This means that for both types of Aggregator Beacons, the coordinator necessarily MUST know all DIDs being aggregated by a cohort.

7.2.3 CID Aggregation Cohort Members Know All DIDs that are Updated

Cohort members participating in a CID Aggregator Beacon learn all DIDs that are updated in each Beacon Signal. This is because they SHOULD verify the contents of the Beacon Signal before authorizing it and a CID Aggregator’s Beacon Signal contains a CID to an Update Bundle. An Update Bundle is a JSON object mapping did:btc1 identifiers to CID values for individual DID Update Payloads. Each DID controller SHOULD independently retrieve and verify the contents of the Update Bundle to ensure it contains the expected update for their DID.

7.2.4 In a System with Non-Repudiation, DID Document History is Revealed

Although it might seem obvious, one of the side effects of using a DID is that a DID controller’s relying party will see their DID Document. In addition, resolving a DID document requires making available to the resolver all the prior DID document updates.

8 References

[BIP-0173] “Base32 address format for native v0-16 witness outputs” https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki

[BIP-0350] “Bech32m format for v1+ witness addresses” https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki

9 Appendix

9.1 Bech32 Encoding and Decoding

did:btc1 uses the Bech32 algorithm to encode and decode several data values. The Bech32 algorithm is documented in BIP-0173. Additionally, did:btc1 uses an updated Bech32 algorithm known as “bech32m” that is documented in BIP-0350. For this specification we define two functions: bech32-encode and bech32-decode.

9.1.1 bech32-encode

This algorithm takes two REQUIRED inputs: a string, hrp which is the human-readable part of the encoding and an array of bytes to be encoded called the dataPart.

  1. Initialize result to the output of Bech32 encoding the hrp and the dataPart as described in BIP-0173.
  2. Return result.

9.1.2 bech32-decode

This algorithm takes one REQUIRED input: a string bech32Str representing a Bech32 encoding data value.

  1. Initialize hrp and dataPart to the result of Bech32 decoding the bech32Str as described in BIP-0173.
  2. Return a tuple (hrp, dataPart).

9.1.3 Bech32 Encoding a secp256k1 Public Key

A macro or convenience function can be used to encode a keyBytes representing a compressed SEC encoded secp256k1 public key. The algorithm takes one REQUIRED input, keyBytes.

  1. Initialize hrp to "k".
  2. Initialize dataPart to keyBytes.
  3. Return the result of the bech32-encode algorithm, passing hrp and dataPart.

9.1.4 Bech32 Encoding a hash-value

A macro or convenience function can be used to encode a hashBytes representing the sha256 hash of an initiating DID document. The algorithm takes one REQUIRED input, hashBytes.

  1. Initialize hrp to "x".
  2. Initialize dataPart to hashBytes.
  3. Return the result of the bech32-encode algorithm, passing hrp and dataPart.

9.2 JSON Canonicalization and Hash

A macro function that takes in a JSON document, document, and canonicalizes it following the JSON Canonicalization Scheme. The function returns the canonicalizedBytes.

  1. Set canonicalBytes to the result of applying the JSON Canonicalization Scheme to the document.
  2. Set hashBytes to the result of applying the SHA256 cryptographic hashing algorithm to the canonicalBytes.
  3. Return hashBytes.

9.3 Fetch Content from Addressable Storage

A macro function that takes in SHA256 hash of some content, hashBytes, converts these bytes to a IPFS v1 Content Identifier and attempts to retrieve the identified content from Content Addressable Storage (CAS).

The function returns the retrieved content or null.

  1. Set cid to the result of converting hashBytes to an IPFS v1 CID.
  2. Set content to the result of fetching the cid from a CAS system. Which CAS systems checked is left to the implementation. TODO: Is this right? Are implementations just supposed to check all CAS they trust?
  3. If content for cid cannot be found, set content to null.
  4. Return content

9.4 Root did:btc1 Update Capabilities

Note: Not sure if these algorithms should go here or in the appendix?

9.4.1 Derive Root Capability from did:btc1 Identifier

This algorithm deterministically generates a ZCAP-LD root capability from a given did:btc1 identifier. Each root capability is unique to the identifier. This root capability is defined and understood by the did:btc1 specification as the root capability to authorize updates to the specific did:btc1 identifiers DID document.

The algorithm takes in a did:btc1 identifier and returns a rootCapability object.

  1. Define rootCapability as an empty object.
  2. Set rootCapability.@context to ‘https://w3id.org/zcap/v1’.
  3. Set encodedIdentifier to result of calling algorithm encodeURIComponent(identifier).
  4. Set rootCapability.id to urn:zcap:root:${encodedIdentifier}.
  5. Set rootCapability.controller to identifier.
  6. Set rootCapability.invocationTarget to identifier.
  7. Return rootCapability.

Below is an example root capability for updating the DID document for did:btc1:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u:

{
  "@context": "https://w3id.org/zcap/v1",
  "id": "urn:zcap:root:did:btc1:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u",
  "controller": "did:btc1:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u",
  "invocationTarget": "did:btc1:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u"
}

9.4.2 Dereference Root Capability Identifier

This algorithm takes in a root capability identifier and dereferences it to the root capability object.

This algorithm takes in a capabilityId and returns a rootCapability object.

  1. Set rootCapability to an empty object.
  2. Set components to the result of capabilityId.split(":").
  3. Validate components:
    1. Assert length of components is 4.
    2. components[0] == urn.
    3. components[1] == zcap.
    4. components[2] == root.
  4. Set uriEncodedId to components[3].
  5. Set btc1Identifier the result of decodeURIComponent(uriEncodedId).
  6. Set rootCapability.id to capabilityId.
  7. Set rootCapability.controller to btc1Identifier.
  8. Set rootCapability.invocationTarget to btc1Identifier.
  9. Return rootCapability.

Below is an example of a didUpdatePayload. An invoked ZCAP-LD capability containing a patch defining how the DID document for did:btc1:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u SHOULD be mutated.

{
  "@context": [
    "https://w3id.org/zcap/v1",
    "https://w3id.org/security/data-integrity/v2",
    "https://w3id.org/json-ld-patch/v1"
  ],
  "patch": [
    {
      "op": "add",
      "path": "/service/4",
      "value": {
        "id": "#linked-domain",
        "type": "LinkedDomains",
        "serviceEndpoint": "https://contact-me.com"
      }
    }
  ],
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "schnorr-secp256k1-jcs-2025",
    "verificationMethod": "did:btc1:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u#initialKey",
    "invocationTarget": "did:btc1:k1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u",
    "capability": "urn:zcap:root:did%3Abtc1%3Ak1q0rnnwf657vuu8trztlczvlmphjgc6q598h79cm6sp7c4fgqh0fkc0vzd9u",
    "capabilityAction": "Write",
    "proofPurpose": "assertionMethod",
    "proofValue": "z381yXYmxU8NudZ4HXY56DfMN6zfD8syvWcRXzT9xD9uYoQToo8QsXD7ahM3gXTzuay5WJbqTswt2BKaGWYn2hHhVFKJLXaDz"
  }
}