Table of Contents

did:btc1 DID Method Specification

Authors:

Contributors:

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.
  • Aggregate 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 Aggregate 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 CIDAggregate 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 id-bech32 value, which is a Bech32m encoding of:

  • the specification version;
  • the Bitcoin network identifier; and
  • either:
    • a key-value representing a secp256k1 public key; or
    • a hash-value representing the hash of an initiating external DID document.

The specification version and the Bitcoin network identifier are encoded into a single byte as follows:

  1. The first four bits (high nibble) are the version minus 1. For this version of the specification, the version is 1 and the high nibble is 0.
  2. The second (remaining) four bits (low nibble) are the Bitcoin network identifier, one of:
    1. 0 = bitcoin (mainnet);
    2. 1 = signet;
    3. 2 = regtest;
    4. 3 = testnet v3;
    5. 4 = testnet v4;
    6. 5-7 = reserved for future use by the specification; or
    7. 8-F = user-defined index into a custom signet network.

The user-defined index into a custom signet network allows any user to stand up a custom signet network and create did:btc1 identifiers on it. However, anyone encountering such an identifier would have to know the details of the network (challenge and seed node) to use it. This means that:

  • The interpretation of a user-defined index is by mutual agreement between parties issuing did:btc1 identifiers and those resolving them.
  • Other users may use the same index, and did:btc1 identifiers will not be interoperable in each other’s network.

When the last part of the encoding is of a key-value, the Human Readable Part (HRP) of the Bech32 encoding is set to k. When the last part of the encoding is of a hash-value, the HRP is set to x. The HRP is followed by a separator which is always 1, which is then followed by the bech32-encoding.

The ABNF for a did:btc1 identifier is as follows:

did-btc1 = "did:btc1:" id-bech32
id-bech32 = key-encoding / hash-encoding
hash-encoding = "x1" bech32-encoding
key-encoding = "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 IETF RFC5234.

3.1 Version Interpretation

The purpose of the version is to identify incompatible changes made in the specification (e.g., a change to the way Beacon signals are constructed and interpreted). The updated specification may also change the way that the did:btc1 identifier is encoded.

There are two consequences. The first is that the version MUST be able to go beyond the domain of a nibble (1-16). To support this, the start of the decoded id-bech32 MUST be interpreted as follows:

  1. Set version to 1.
  2. Start with the first nibble (the higher nibble of the first byte).
  3. Add the value of the current nibble to version.
  4. If the value of the nibble is hexadecimal F (decimal 15), advance to the next nibble (the lower nibble of the current byte or the higher nibble of the next byte) and return to the previous step.

More simply:

version = 1 + sum of all nibbles up to and including first non-hexadecimal-F

What appears beyond the version is version-specific. Implementations MUST NOT attempt to interpret did:btc1 identifiers with an unknown version.

For illustration purposes only, assume that the nibble following the version always represents the network and that the allowable values for the network don’t change. The interpretation of the version and network would be as follows (spaces between bytes added for readability):

Decoded bytes Version Network
00 03 c7 … 1 bitcoin
23 03 c7 … 3 testnet v3
C9 03 c7 … 13 custom signet 2
F2 90 03 c7 … 18 custom signet 2
FF 72 03 c7 … 38 regtest

3.2 did:btc1 Identifier Encoding

Given:

  • idType - required, one of:
    • “key”
    • “external”
  • version - required, number
  • network - required, one of:
    • “bitcoin”
    • “signet”
    • “regtest”
    • “testnet3”
    • “testnet4”
    • number
  • genesisBytes - required, byte array, one of:
    • a compressed secp256k1 public key if idType is “key”
    • a hash of an initiating external DID document if idType is “external”

Encode the did:btc1 identifier as follows:

  1. If idType is not a valid value per above, raise invalidDid error.
  2. If version is greater than 1, raise invalidDid error.
  3. If network is not a valid value per above, raise invalidDid error.
  4. if network is a number and is outside the range of 1-8, raise invalidDid error.
  5. If idType is “key” and genesisBytes is not a valid compressed secp256k1 public key, raise invalidDid error.
  6. Map idType to hrp from the following:
    1. “key” - “k”
    2. “external” - “x”
  7. Create an empty nibbles numeric array.
  8. Set fCount equal to (version - 1) / 15, rounded down.
  9. Append hexadecimal F (decimal 15) to nibbles fCount times.
  10. Append (version - 1) mod 15 to nibbles.
  11. If network is a string, append the numeric value from the following map to nibbles:
    1. “bitcoin” - 0
    2. “signet” - 1
    3. “regtest” - 2
    4. “testnet3” - 3
    5. “testnet4” - 4
  12. If network is a number, append network + 7 to nibbles.
  13. If the number of entries in nibbles is odd, append 0.
  14. Create a dataBytes byte array from nibbles, where index is from 0 to nibbles.length / 2 - 1 and encodingBytes[index] = (nibbles[2 * index] << 4) | nibbles[2 * index + 1].
  15. Append genesisBytes to dataBytes.
  16. Set identifier to “did:btc1:”.
  17. Pass hrp and dataBytes to the Bech32m Encoding algorithm, retrieving encodedString.
  18. Append encodedString to identifier.
  19. Return identifier.

3.3 did:btc1 Identifier Decoding

Given:

  • identifier - required, a string did:btc1 identifier

Decode the did:btc1 identifier as follows:

  1. Split identifier into an array of components at the colon : character.
  2. If the length of the components array is not 3, raise invalidDid error.
  3. If components[0] is not “did”, raise invalidDid error.
  4. If components[1] is not “btc1”, raise methodNotSupported error.
  5. Set encodedString to components[2].
  6. Pass encodedString to the Bech32m Decoding algorithm, retrieving hrp and dataBytes.
  7. If the Bech32m decoding algorithm fails, raise invalidDid error.
  8. Map hrp to idType from the following:
    1. “k” - “key”
    2. “x” - “external”
    3. other - raise invalidDid error
  9. Set version to 1.
  10. If at any point in the remaining steps there are not enough nibbles to complete the process, raise invalidDid error.
  11. Start with the first nibble (the higher nibble of the first byte) of dataBytes.
  12. Add the value of the current nibble to version.
  13. If the value of the nibble is hexadecimal F (decimal 15), advance to the next nibble (the lower nibble of the current byte or the higher nibble of the next byte) and return to the previous step.
  14. If version is greater than 1, raise invalidDid error.
  15. Advance to the next nibble and set networkValue to its value.
  16. Map networkValue to network from the following:
    1. 0 - “bitcoin”
    2. 1 - “signet”
    3. 2 - “regtest”
    4. 3 - “testnet3”
    5. 4 - “testnet4”
    6. 5-7 - raise invalidDid error
    7. 8-F - networkValue - 7
  17. If the number of nibbles consumed is odd:
    1. Advance to the next nibble and set fillerNibble to its value.
    2. If fillerNibble is not 0, raise invalidDid error.
  18. Set genesisBytes to the remaining dataBytes.
  19. If idType is “key” and genesisBytes is not a valid compressed secp256k1 public key, raise invalidDid error.
  20. Return idType, version, network, and genesisBytes.

3.4 Differentiating did:btc1 Identifiers

This section is non-normative.

It is sometimes useful to differentiate between production (bitcoin network) and test (other network) did:btc1 identifiers without having to go through the decoding process.

Bech32 encodes five bits at a time, so the version and network (8 bits, assuming version is 15 or below) can be interpreted by looking at the first two characters. How that manifests depends on the HRP.

With HRP k, there is the additional advantage that the first nibble of the public key is always zero (because the first byte is either 02 or 03, indicating the sign). That means that, for version 1 on bitcoin network, the first three nibbles (12 bits) are zero, which translates to “qq” (five bits zero followed by five bits zero), with two bits (also zero) left over. Any did:btc1:k1qq... pattern is therefore version 1 on bitcoin.

HRP x is a little more complicated, because the extra two bits for the second block of five bits could be any of four values. Therefore, there are four two-character strings that could appear after the ‘1’ separator: “qq”, “qp”, “qz”, and “qr”. Any did:btc1:x1qq..., did:btc1:x1qp..., did:btc1:x1qz..., or did:btc1:x1qr... pattern is therefore version 1 on bitcoin.

If the version changes, the strings change as well. Version 2 on bitcoin would encode HRP k as “zq” and HRP x as “zq”, “zp”, “zz”, or “zr”.

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 idType to “key”.
  2. Set version to 1.
  3. Set network to the desired network.
  4. Set genesisBytes to pubKeyBytes.
  5. Pass idType, version, network, and genesisBytes to the did:btc1 Identifier Encoding algorithm, retrieving id.
  6. Set did to id.
  7. Set initialDocument to the result of passing did into the Read algorithm.
  8. 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 MUST 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 MUST 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 version to 1.
  3. Set network to the desired network.
  4. Set genesisBytes to the result of passing intermediateDocument into the JSON Canonicalization and Hash algorithm.
  5. Pass idType, version, network, and genesisBytes to the did:btc1 Identifier Encoding algorithm, retrieving id.
  6. Set did to id.
  7. Set initialDocument to a copy of the intermediateDocument.
  8. Replace all did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx values in the initialDocument with the did.
  9. 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.
  10. Return did and initialDocument.

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. Pass identifier to the did:btc1 Identifier Decoding algorithm, retrieving idType, version, network, and genesisBytes.
  2. Set identifierComponents to a map of idType, version, network, and genesisBytes.
  3. Set initialDocument to the result of running the algorithm in Resolve Initial Document passing in the identifier, identifierComponents and resolutionOptions.
  4. Set targetDocument to the result of running the algorithm in Resolve Target Document passing in initialDocument and resolutionOptions.
  5. Return targetDocument.

4.2.1 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.idType value is “key”, then set the initialDocument to the result of running the algorithm in Deterministically Generate Initial DID Document passing in the identifier and identifierComponents values.
  2. Else If identifierComponents.idType value is “external”, 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.1.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 an identifierComponents object and returns an initialDocument.

  1. Set keyBytes to identifierComponents.genesisBytes.
  2. Initialize an initialDocument variable as an empty object.
  3. Set initialDocument.id to the identifier.
  4. Set initialDocument.controller to the identifier
  5. Initialize a contextArray to empty array:
    1. Append the DID Core v1.1 context “https://www.w3.org/ns/did/v1.1”.
    2. Append a did:btc1 context.
    3. Set initialDocument['@context]' to contextArray.
  6. Create an initial verification method:
    1. Initialize verificationMethod to an empty object.
    2. Set verificationMethod.id to {identifier}#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.
  7. Set initialDocument.verificationMethod to an array containing verificationMethod.
  8. Initialize a tempArray variable to an array with the single element verificationMethod.id.
  9. Set the authentication, assertionMethod, capabilityInvocation, and the capabilityDelegation properties in initialDocument to a copy of the tempArray variable.
  10. Set the initialDocument.services property in initialDocument to the result of passing the identifier, keyBytes and identifierComponents.network to the Deterministically Generate Beacon Services algorithm.
  11. Return initialDocument.
4.2.1.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 {identifier}#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 {identifier}#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 {identifier}#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.1.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, an 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.1.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 an 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.1.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.2 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 into 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 the UNIX timestamp for now at the moment of execution.
  4. Set signalsMetadata to resolutionOptions.sidecarData.signalsMetadata.
  5. Set currentVersionId to 1.
  6. If currentVersionId equals targetVersionId return initialDocument.
  7. Set updateHashHistory to an empty array.
  8. Set contemporaryBlockheight to 0.
  9. Set contemporaryDIDDocument to the initialDocument.
  10. Set targetDocument to the result of calling the Traverse Blockchain History algorithm passing in contemporaryDIDDocument, contemporaryBlockheight, currentVersionId, targetVersionId, targetTime, updateHashHistory, signalsMetadata, and network.
  11. Return targetDocument.

4.2.2.1 Traverse Blockchain History

This algorithm traverses this history of the Bitcoin blockchain, starting from the block with the blockheight equal to contemporaryBlockheight, to find beaconSignals emitted by Beacons specified within the contemporaryDIDDocument. Each beaconSignal is processed to retrieve a DID Update Payload defining updates to the DID document. Each update is applied to the document and duplicates are ignored. The algorithm recursively executes until either a targetVersionId for the DID document is reached, or the blockchain history passes the supplied targetTime. At this point the current contemporaryDIDDocument is returned.

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.
  • targetTime: A 64-bit UNIX timestamp that can be used to target specific historical states of a DID document. Only Beacon Signals included in the Bitcoin blockchain before the targetTime are processed by the resolution algorithm.
  • 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 targetTime 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 contemporaryBlockheight, beacons and network.
  5. If nextSignals is empty, return contemporaryDIDDocument.
  6. If nextSignals[0].blocktime is greater than targetTime, return contemporaryDIDDocument.
  7. Set contemporaryBlockheight to nextSignals[0].blockheight.
  8. Set updates to the result of calling algorithm Process Beacon Signals passing in nextSignals and signalsMetadata.
  9. Set orderedUpdates to the list of updates ordered by the targetVersionId property.
  10. For update in orderedUpdates:
    1. If update.targetVersionId is less than or equal to currentVersionId, run the Confirm Duplicate Update Algorithm 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.
  11. Increment contemporaryBlockheight.
  12. Set targetDocument to the result of calling the Traverse Blockchain History algorithm passing in contemporaryDIDDocument, contemporaryBlockheight, currentVersionId, targetVersionId, targetTime, updateHashHistory, signalsMetadata, and network.
  13. Return targetDocument.

4.2.2.2 Find Next Signals

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

Note: It is recommended that you use a Bitcoin indexer and API such as electrs or Esplora to query the Bitcoin blockchain.

This algorithm takes in the following inputs:

  • contemporaryBlockheight: The height of the block this function is looking for Beacon Signals in. 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 array of signal structs with the following properties:

  • beaconId: The id for the Beacon that the Beacon Signal was announced by.
  • beaconType: The type of the Beacon that announced the Beacon Signal.
  • tx: The Bitcoin transaction that is the Beacon Signal.
  • blockheight: The blockheight for the block that the Bitcoin transaction was included within.
  • blocktime: The timestamp that the Bitcoin block was included into the blockchain.
  1. Set signals to an empty array.
  2. For each beacon in beacons:
    1. Set beaconSpends to the set of all Bitcoin transactions on the specified network that spend at least one transaction input controlled by the beacon.address with a blockheight greater than or equal to the contemporaryBlockheight.
    2. Filter the beaconSpends, identifying all transactions whose 0th transaction output is of the format [OP_RETURN, OP_PUSH32, <32bytes>].
    3. For each of the filtered beaconSpends push the following beaconSignal object onto the signals array.
    {
      "beaconId": "${beaconService.id}",
      "beaconType": "${beaconService.type}",
      "tx": "${tx}",
      "blockheight": "${blockheight}",
      "blocktime": "${blocktime}"
    }
  3. If signals is empty, return signals.
  4. Sort signals by blockheight from lowest to highest.
  5. Set nextSignals to all signals with the lowest blockheight.
  6. Return nextSignals.

4.2.2.3 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.2.4 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.2.5 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 bip340-jcs-2025 cryptosuite instance using the key referenced by the verificationMethod field in the update.
   {
   "type": "DataIntegrityProof",
   "cryptosuite": "bip340-jcs-2025",
   "verificationMethod": "did:btc1:x1q20n602dgh7awm6akhgne0mjcmfpnjpc9jrqnrzuuexglrmklzm6u98hgvp#key-1",
   "proofPurpose": "capabilityInvocation",
   "capability": "urn:zcap:root:did%3Abtc1%3Ax1q20n602dgh7awm6akhgne0mjcmfpnjpc9jrqnrzuuexglrmklzm6u98hgvp",
   "capabilityAction": "Write",
   "@context": [
      "https://w3id.org/security/v2",
      "https://w3id.org/zcap/v1",
      "https://w3id.org/json-ld-patch/v1"
   ],
   "proofValue": "z3SvqrVEjVur24zh1vnYHB3SxQPMvxXP1XMxjtqBptezKASXtUUTsotQh2rabGLDyBf8riJkcL9wbHkZjDRSYRVYC"
}
  1. Set expectedProofPurpose to capabilityInvocation.
  2. Set mediaType to ???? TODO: is this just application/json?
  3. Set documentBytes to the bytes representation of update.
  4. Set verificationResult to the result of passing mediaType, documentBytes, cryptosuite, and expectedProofPurpose into the Verify Proof algorithm defined in the VC Data Integrity specification.
  5. If verificationResult.verified equals False, MUST raise an invalidUpdateProof exception.
  6. Set targetDIDDocument to a copy of contemporaryDIDDocument.
  7. Use JSON Patch to apply the update.patch to the targetDIDDOcument.
  8. Verify that targetDIDDocument is conformant with the data model specified by the DID Core specification.
  9. Set targetHash to the result of passing targetDIDDocument to the JSON Canonicalization and Hash algorithm.
  10. Check that targetHash equals the base58 decoded update.targetHash, else raise InvalidDidUpdate error.
  11. 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 bip340-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 BIP340 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 BIP340 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 bip340-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 BIP340 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 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 the 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 an 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. Set hashBytes to the 32 bytes in the txOut.
  4. 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
  5. 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.
  6. Return didUpdatePayload.

5.2 CIDAggregate Beacon

A Beacon of the type CIDAggregateBeacon 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 CIDAggregate Beacon, 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 Authorize 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 opting in to the Beacon cohort.

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 an 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. Set hashBytes to the 32 bytes in the txOut.
  4. 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.
  5. 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.
  6. 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 SMTAggregate 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:btc1 identifier being resolved. Or the Sidecar data MUST contain a proof that the leaf indexed by the did:btc1 identifier is empty, thereby proving that the SMT 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 an 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. If no signalSidecarData, MUST raise an incompleteSidecarData error. MAY identify the Beacon Signal to resolver and request additional Sidecar data be provided.
  3. Set smtProof to signalSidecarData.smtProof.
  4. If no smtProof, MUST raise a latePublishing error.
  5. Set smtRoot to the 32 bytes of data in txOut.
  6. Set didUpdatePayload to signalSidecarData.updatePayload.
  7. Set updateHashBytes to the result of passing didUpdatePayload to the JSON Canonicalization and Hash algorithm.
  8. Set identifierBytes to the result of converting btc1Identifier to bytes.
  9. 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.
  10. 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 are 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 cannot 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 Aggregate Beacon Address Verification

An Aggregate 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 Aggregate 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 Aggregate 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 Aggregate Beacons, the coordinator necessarily MUST know all DIDs being aggregated by a cohort.

7.2.3 CIDAggregate Cohort Members Know All DIDs that are Updated

Cohort members participating in a CIDAggregate 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 CIDAggregate 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 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 Bech32m Encoding and Decoding

did:btc1 uses the Bech32m algorithm to encode and decode several data values. The original Bech32 algorithm is documented in BIP-0173. The updated algorithm, Bech32m, is documented in BIP-0350.

9.1.1 Bech32m Encoding

Given:

  • hrp - required, a string representing the Human-Readable Part of the encoding
  • dataBytes - required, a byte array to be encoded
  1. Initialize encodedString to the output of Bech32m encoding the hrp and the dataBytes as described in BIP-0350.
  2. Return encodedString.

9.1.2 Bech32m Decoding

Given:

  • encodedString - required, the Bech32m-encoded string from a prior encoding operation
  1. Initialize hrp and dataBytes to the result of Bech32m decoding the encodedString as described in BIP-0350.
  2. Return hrp and dataBytes.

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:k1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx",
  "controller": "did:btc1:k1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx",
  "invocationTarget": "did:btc1:k1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx"
}

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:k1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx#initialKey",
    "invocationTarget": "did:btc1:k1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx",
    "capability": "urn:zcap:root:did%3Abtc1%3Ak1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx",
    "capabilityAction": "Write",
    "proofPurpose": "assertionMethod",
    "proofValue": "z381yXYmxU8NudZ4HXY56DfMN6zfD8syvWcRXzT9xD9uYoQToo8QsXD7ahM3gXTzuay5WJbqTswt2BKaGWYn2hHhVFKJLXaDz"
  }
}