Table of Contents

did:btc1 DID Method Specification

WARNING
This specification is still under active development and may be subject to breaking changes.
Once we have finalized the specification text a stable v1.0 of the specification will be published.

Authors

Contributors

Publication Date

20th September 2024

Licence Statement

Mozilla Public License Version 2.0

© 2024 Digital Contract Design


Abstract

did:btc1 is a censorship-resistant Decentralized Identifier (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 Pretty Good Privacy’s (PGP) foundational legal work in the 1990s. Since the late 2010s, with Decentralized Identifiers (DIDs), 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

The Bitcoin Reference DID method (BTCR) is the original Bitcoin based 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 the InterPlanetary File System (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.1.5.1 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.1.5.2 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 BTC1 Beacons’ updates (although many BTC1 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 BTC1 Beacons will choose Bitcoin scripts that allow every DID controller a veto, although given current Unspent Transaction Output (UTXO)-sharing technology, this impedes availability.

1.1.5.3 Future Directions

  • Authorization Capabilities (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
  • BTC1 Beacons do not have to reuse their addresses if, in the controller’s DID document, a descriptor is used instead of an address.

2 Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words MAY, MUST, MUST NOT, RECOMMENDED, SHOULD, and SHOULD NOT in this document are to be interpreted as described in BCP 14 RFC2119 RFC8174 when, and only when, they appear in all capitals, as shown here.

This document contains examples that contain JSON and JSON-LD content. Some of these examples contain characters that are invalid, such as inline comments (//) and the use of ellipsis (…) to denote information that adds little value to the example. Implementers are cautioned to remove this content if they desire to use the information as valid JSON or JSON-LD.

Interoperability of implementations of the did:btc1 DID method is tested by evaluating an implementation’s ability to create, update, deactivate, and resolve did:btc1 identifier and DID documents that conform to this specification. Interoperability for producers and consumers of did:btc1 identifier and DID documents is provided by ensuring the DIDs and DID documents conform.

A conforming did:btc1 DID is any concrete expression of the rules specified in Syntax which complies with relevant normative statements in that section.

A conforming did:btc1 DID document is any concrete expression of the data model described in this specification which complies with the relevant normative statements in DID core sections 4. Data Model and 5. Core Properties. A serialization format for the conforming document is deterministic, bi-directional, and lossless, as described in 6. Representations.

A conforming registrar is any algorithm realized as software and/or hardware that generates and updates conforming did:btc1 identifier or conforming DID Documents and complies with the relevant normative statements in 6. Representations of DID core and the Create, Update and Deactivate sections of this specification.

A conforming did:btc1 resolver is any algorithm realized as software and/or hardware that complies with the relevant normative statements in 4. DID Resolution of the DID Resolution specification and the Read section of this specification.

3 Terminology

BTC1 Beacon

A abstract mechanism, identified by a Beacon Address, that is included as a service in a DID document to indicate to resolvers that spends from this address, called Beacon Signals, should be discovered and checked for BTC1 Update Announcements.

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 BTC1 Updates in a Beacon Signal.

There can only ever be one BTC1 Update 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 BTC1 Beacon address and include a transaction output of the format [OP_RETURN, <32_bytes>]. Beacon Signals announce one or more BTC1 Updates and provide a means for these payloads to be verified as part of the Beacon Signal.

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

Authorized Beacon Signal

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

BTC1 Update Announcement

A 32 byte SHA256 hash committing to a BTC1 Update that has been broadcast by a BTC1 Beacon in an Authorized Beacon Signal. Beacon Signals can include one or more BTC1 Update Announcements. How Beacon Signals include announcements is defined by the Beacon Type.

BTC1 Update

A capability invocation secured using Data Integrity that invokes an authorization capability to update a specific did:btc1 DID document. This capability invocation Data Integrity proof secures the Unsecured BTC1 Update document.

Unsecured BTC1 Update

A BTC1 Update without a proof attached to it invoking the capability to apply the update to a did:btc1 DID document. An Usecured BTC1 Update contains the JSON Patch object that defines the set of mutations to be applied to a DID document, along with the new version of the DID document and the source and target hashes of the DID document identifying the source DID document that the patch should be applied to and the target DID document that results from appliying the patch.

Pending BTC1 Update

A BTC1 Update that has not yet been announced in an Authorized Beacon Signal.

Announced BTC1 Update

A BTC1 Update that has been announced in an Authorized Beacon Signal which has met the specified threshold for confirmation.

Beacon Type

The type of a BTC1 Beacon. The Beacon Type defines how BTC1 Update Announcements are included within a Beacon Signal broadcast on the Bitcoin network. It also defines how the content committed within BTC1 Update Announcements can be verified against the Beacon Signal.

Singleton Beacon

A type of BTC1 Beacon whose Beacon Signals each contain a single BTC1 Update Announcement.

Map Beacon

A type of BTC1 Beacon which aggregates multiple BTC1 Update Announcements. A Beacon Signal from a Map Beacon commits to a Beacon Announcement Map.

Beacon Announcement Map

A document that maps did:btc1 identifiers to BTC1 Update Announcements. This is used to distinguish which BTC1 Update Announcement applies to which did:btc1 identifier.

SMT Beacon

A type of BTC1 Beacon which aggregates multiple BTC1 Update Announcements.
A Beacon Signal from an SMT Beacon contains the root of an optimized Sparse Merkle Tree.

Beacon Cohort

The set of unique cryptographic keys participating in a BTC1 Beacon that are required to authorize spends from the Beacon Address.

Beacon Aggregator

The entity that coordinates the protocols of an aggregate BTC1 Beacon. Specifically the Create Beacon Cohort and Announce Beacon Signal protocols.

Beacon Participant

A member of a Beacon Cohort, typically a DID controller, that controls cryptographic keys required to partially authorize spends from a Beacon Address.

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 Merkle Tree data structure 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 BTC1 Update for that did:btc1 identifier that indexed to the leaf.

Invocation

See Authorization Capabilities for Linked Data v0.3

Schnorr Signature

An alternative to Elliptic Curve Digital Signature Algorithm (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 Coordinated Universal Time (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 BTC1 Update applied to the DID document.

Intermediate DID Document

A representation of a DID document that it not yet fully conformant with the DID Core specification. Intermediate DID documents for the did:btc1 DID method are DID documents whose identifier values have been replaced with a placeholder value.

Initial DID Document

The canonical, conformant version 1 of a DID document for a specific did:btc1 identifier.

4 Syntax

A did:btc1 Decentralized Identifier (DID) consists of a did:btc1 prefix, followed by an id-bech32 value, which is a Bech32m encoding of the following data:

  • version - the specification version the DID was created against;
  • network - the Bitcoin network the DID can be used on; 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 are encoded into a single byte as follows:

  1. The first four bits (high nibble) are the version minus one. 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 = mutinynet;
    7. 6-B = reserved for future use by the specification; or
    8. C-F = user-defined index into a custom test network.

The user-defined index allows any user to stand up a custom test network and create did:btc1 identifiers on it. However, anyone encountering such an identifier would have to know the details of the network (e.g., challenge and seed node for signet) 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 Augmented Backus-Naur (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 Internet Engineering Task Force (IETF) RFC5234.

4.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
CD 03 c7 … 13 custom test network 2
F2 D0 03 c7 … 18 custom test network 2
FF 72 03 c7 … 38 regtest

4.2 did:btc1 Identifier Encoding

Given:

  • idType - required, one of:
    • “key”
    • “external”
  • version - required, number
  • network - required, one of:
    • “bitcoin”
    • “signet”
    • “regtest”
    • “testnet3”
    • “testnet4”
    • “mutinynet”
    • 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-4, 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
    6. “mutinynet” - 5
  12. If network is a number, append network + 11 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.

4.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 - “mutinynet”
    7. 6-B - raise invalidDid error
    8. C-F - networkValue - 11
  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.

4.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”.

5 File Sharing

All files in this specification, except for deterministically generated initial DID documents, are identified by their SHA256 cryptographic hash calculated according to the JSON Canonicalization and Hash algorithm. As a resolver goes through the resolution process, it encounters one or more document hashes, which it uses to identify the files of interest. There are two ways in which these files may be shared: through a Sidecar mechanism or using Content Addressable Storage (CAS).

While it’s possible for a single did:btc1 identifier to mix the two file sharing mechanisms, it is not recommended.

5.1 Sidecar

Sidecar is a mechanism by which a file is provided alongside the did:btc1 identifier being resolved. This is analogous to a sidecar on a motorcycle bringing along a second passenger: the DID controller provides the DID document history (in the form of JSON Patch transformations) alongside the DID to the relying party so that the resolver can construct the DID document.

In short, when a resolver is presented with a did:btc1 identifier, it is also presented with files matching the SHA256 hashes it encounters during the resolution process. If any SHA256 hash doesn’t have a corresponding file, the resolution fails.

5.2 Content Addressable Storage (CAS)

Content Addressable Storage (CAS) is a mechanism by which a file stored is addressed by its content, not its name or location. The content address is determined by a cryptographic hash of the file. The hash is then passed into a retrieval function specific to the type of CAS to retrieve the file.

Any CAS that provides a deterministic mapping from a SHA256 hash of a file may be used, and a resolver SHOULD be informed of the specific CAS mechanism so that it can retrieve documents associated with a did:btc1 identifier efficiently. If the CAS mechanism is not provided, the resolver MAY iterate through all supported CAS mechanisms to find the files or it MAY return with an error indicating that the CAS mechanism is required.

At this time, IPFS is the only known CAS to provide a deterministic mapping from a SHA256 hash. Others may be documented in future, but the absence of any CAS from this or any future version of this specification MUST NOT impede its usage, provided there is agreement between the DID controller, relying party, and resolver on the use of such an algorithm.

5.2.1 Interplanetary File System (IPFS)

The Interplanetary File System (IPFS) “is a set of open protocols for addressing, routing, and transferring data on the web, built on the ideas of content addressing and peer-to-peer networking.”

A detailed description is available at the IPFS documentation site, but for the purposes of did:btc1, it is a distributed file system where the files are identified using a unique Content Identifier (CID) based on the content of the file. The content of the file determines the CID, and the CID may be used by anyone, anywhere, to retrieve the file.

As with disk-based file systems, it’s not practical to stream an entire file into a contiguous block, so files are broken up into smaller blocks. The entire file is structured as a Merkle Directed Acyclic Graph (DAG), where each node is a block of data and a list of CIDs of its children.

At its simplest, the CID is a cryptographic hash, not of the file, but of its first block. Even if the block has no children, it still has metadata indicating this, so the hash doesn’t equal the hash of the file.

That’s the default behaviour; it’s possible to override the chunking behaviour by storing the file as a raw binary using the Raw Leaves option. This limits the file size to the block size (default 256 kB, maximum 1 MB), but that should be sufficient for most applications.

For did:btc1 identifiers, files stored in IPFS SHALL be stored using the Raw Leaves option.

The IPFS CIDv1 is a binary identifier constructed from the file hash as:

  • 0x01, the code for CIDv1;
  • 0x00, the multicodec code for raw binary;
  • 0x12, the multihash code for SHA-256; and
  • the SHA-256 of the file.

The stringified version of the CIDv1 is accomplished using multibase encoding. The final URL is “ipfs://<stringified CIDv1>”.

A resolver retrieves a file associated with a SHA256 hash by constructing the IPFS CIDv1 per the above algorithm and requesting the file from an IPFS node.

6 Beacons

6.1 Overview

A BCT1 Beacon is an abstract mechanism, identified by a Bitcoin address, that is included as a service in a DID document to indicate to resolvers that spends from the address, called Beacon Signals, should be checked for BTC1 Update Announcements.

All Beacon Signals broadcast from a BTC1 Beacon MUST be processed as part of DID document resolution. The Beacon type in the service defines how Beacon Signals MUST be processed.

When defining a service for a BTC1 Beacon:

  • type is “BTC1Beacon”
  • beaconType is one of “SingletonBeacon”, “MapBeacon”, or “SMTBeacon”
  • serviceEndpoint is a Bitcoin address represented as a URI following the BIP21 scheme

How the Bitcoin address and the cryptographic material that controls it are generated is left to the implementation.

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

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

This is an extensible mechanism, such that in the future new Beacon Types could be added.

The current, active BTC1 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 BTC1 Beacons they use to broadcast updates to their DID document over time. Resolution of a DID MUST process signals from all BTC1 Beacons identified in the latest DID document and apply them in the order determined by the version specified by the btc1Update.

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

A Beacon Signal commits to and anchors in a Bitcoin block 32 bytes of information that represents one of the following:

6.2 Actors

Actors in signaling are as follows:

  • DID controller - A party that controls one or more did:btc1 identifiers participating in a BTC1 Beacon.
  • Beacon Cohort - The set of unique cryptographic keys participating in a BTC1 Beacon that make up its n-of-n MuSig2 Bitcoin address.
  • Beacon Aggregator - The entity that coordinates the protocols of an aggregate BTC1 Beacon, specifically the “Create Beacon Cohort” and “Announce Beacon Signal” protocols.
  • Beacon Participant - A member of a Beacon Cohort, typically a DID controller, that controls cryptographic keys required to partially authorize the broadcasting of a Beacon Signal to the Bitcoin blockchain.
  • Verifier - A party verifying a did:btc1 identifier presentation.

6.3 Aggregation

Three types of BTC1 Beacons are defined: SingletonBeacon, MapBeacon and SMTBeacon. Two of them, MapBeacon and SMTBeacon, support aggregation, i.e. the act of committing to multiple BTC1 Update Announcements in a Beacon Signal.

How coordination between an aggregator and multiple Beacon participants is managed is out of scope, but one possible mechanism is outlined in “MuSig2 3-of-3 Multisig with Coordinator Facilitation” at MuSig2 Sequence Diagrams.

When defining a Beacon Cohort, the Beacon Aggregator may define the conditions for the cohort, including but not limited to:

  • Automatic publication to CAS (Map Beacon only)
  • Minimum and/or maximum number of Beacon participants
  • Minimum and/or maximum number of DIDs per Beacon participant
  • Cost of enrollment
  • Cost per signal per DID or Beacon participant
  • Minimum and/or maximum time between signals
  • Number of pending updates that trigger a signal

6.4 Singleton Beacon

A Singleton Beacon is a BTC1 Beacon that can be used to announce commitments to a single BTC1 Update targeting a single DID document. It creates a Beacon Signal that commits to a single BTC1 Update Announcement. This is typically done directly by the DID controller, as there is no Beacon cohort.

If the BTC1 Update committed to by the BTC1 Update Announcement is not publicly discoverable (i.e., is not published to a CAS under its hash), the only parties that are aware of it are the DID controller and any parties provided it by the DID controller.

The beaconType of the service for a Singleton Beacon is “SingletonBeacon”.

{
    "type": "SingletonBeacon",
    "id": "did:btc1:k1qypcylxwhf8sykn2dztm6z8lxm43kwkyzf07qmp9jafv3zfntmpwtks9hmnrw#initialP2PKH",
    "serviceEndpoint": "bitcoin:moZUuvjVnipp1jd6y6a2vdhjLjdaWpuTCv"
}

6.4.1 Construct and Send Beacon Signal

Given:

  • network - required, one of:
    • “bitcoin”
    • “signet”
    • “regtest”
    • “testnet3”
    • “testnet4”
    • “mutinynet”
    • number
  • serviceEndpoint - required, a Bitcoin address represented as a URI
  • One and only one of, required:
  • cas - optional, one of:
    • “ipfs”

Construct a Bitcoin transaction that spends from the Beacon address on the selected network:

  1. If network is not a valid value per above, raise InvalidParameter error.
  2. if network is a number and is outside the range of 1-4, raise InvalidParameter error.
  3. If cas is defined and is not a valid value per above, raise InvalidParameter error.
  4. Set bitcoinAddress to the decoding of serviceEndpoint following BIP21.
  5. Ensure bitcoinAddress is funded; if not, fund this address.
  6. If btc1UpdateAnnouncement is not defined, set btc1UpdateAnnouncement to the result of passing btc1Update to the JSON Canonicalization and Hash algorithm.
  7. Initialize spendTx to a Bitcoin transaction that spends a transaction controlled by the bitcoinAddress and contains at least one transaction output. This signal output MUST have the format [OP_RETURN, OP_PUSHBYTES32, <btc1UpdateAnnouncement>]. If the transaction contains multiple transaction outputs, the signal output MUST be the last transaction output.
  8. Retrieve the cryptographic material, e.g., private key or signing capability, associated with the bitcoinAddress. How this is done is left to the implementer.
  9. Sign the spendTx.
  10. Broadcast spendTx on the Bitcoin network defined by network.
  11. If cas and btc1Update are defined, publish btc1Update to the CAS network defined by cas.

6.5 Map Beacon

A Map Beacon creates a Beacon Signal that commits to multiple BTC1 Update Announcements through a Beacon Announcement Map. To do so, it constructs a map where the key is the did:btc1 identifier and the value is the hash of the corresponding BTC1 Update, and broadcasts a SHA256 hash of the map in the Beacon Signal.

If a BTC1 Update is not publicly discoverable (i.e., is not published to a CAS under its hash), the only parties that are aware of it are the DID controller and any parties provided it by the DID controller. However, any party that has access to or is provided the map is at least aware of the existence of all did:btc1 identifiers in the map and the existence of their BTC1 Update Announcements.

For a Map Beacon, proof of non-inclusion of a did:btc1 identifier is simply its absence from the map.

The beaconType of the service for a Map Beacon is “MapBeacon”.

6.5.1 Create Beacon Cohort

Creating a Beacon Cohort requires that the Beacon Aggregator define the conditions for it, advertise it, and accept enrolment by Beacon Participants. The process flow involves the exchange of data between the Beacon Aggregator and Beacon Participants, most notably the DIDs involved and the public keys for creating the n-of-n MuSig2 Bitcoin address. A detailed specification is out of scope, but most implementations operate as shown below.

6.5.2 Construct and Send Beacon Signal

Constructing and sending a Map Beacon signal operates roughly as follows:

6.5.2.1 Create Beacon Announcement Map

Given:

  • unnormalizedBeaconAnnouncementMap - required, a map of key-value pairs consisting of:
    • did - required, a unique did:btc1 identifier (key)
    • One and only one of, required (value):

Create a Beacon Announcement Map as follows:

  1. If unnormalizedBeaconAnnouncementMap contains a duplicate did, raise InvalidParameter error.
  2. Create empty beaconAnnouncementMap.
  3. For each did in unnormalizedBeaconAnnouncementMap:
    1. If the value is a BTC1 Update:
      1. Set hashBytes to the result of passing btc1Update to the JSON Canonicalization and Hash algorithm.
      2. Set hashString to the hexadecimal string representation of hashBytes.
    2. If the value is a SHA256 hash in binary form:
      1. Set hashString to the hexadecimal string representation of btc1UpdateAnnouncement.
    3. Add did (key) and hashString (value) to beaconAnnouncementMap.

6.5.2.2 Construct Unsigned Beacon Signal

Given:

  • network - required, one of:
    • “bitcoin”
    • “signet”
    • “regtest”
    • “testnet3”
    • “testnet4”
    • “mutinynet”
    • number
  • serviceEndpoint - required, a Bitcoin address represented as a URI
  • beaconAnnouncementMap - required, Beacon Announcement Map created as above

Construct a Bitcoin transaction that spends from the Beacon address on the selected network:

  1. If network is not a valid value per above, raise InvalidParameter error.
  2. if network is a number and is outside the range of 1-4, raise InvalidParameter error.
  3. Set bitcoinAddress to the decoding of serviceEndpoint following BIP21.
  4. Ensure bitcoinAddress is funded; if not, fund this address.
  5. Set hashBytes to the result of passing the JSON representation of beaconAnnouncementMap to the JSON Canonicalization and Hash algorithm.
  6. Initialize unsignedSpendTx to a Bitcoin transaction that spends a transaction controlled by the bitcoinAddress and contains at least one transaction output. This signal output MUST have the format [OP_RETURN, OP_PUSHBYTES32, <hashBytes>]. If the transaction contains multiple transaction outputs, the signal output MUST be the last transaction output.

6.5.2.3 Validate Beacon Announcement Map and Unsigned Beacon Signal

Given:

  • beaconAnnouncementMap - required, Beacon Announcement Map created as above
  • unsignedSpendTx - required, unsigned Beacon signal constructed as above

Validate the Beacon Announcement Map and the unsigned Beacon signal:

  1. Validate that beaconAnnouncementMap contains each DID previously sent and that the value associated with each DID is either the hash previously sent or the hash of the data previously sent.
  2. Set hashBytes to the result of passing the JSON representation of beaconAnnouncementMap to the JSON Canonicalization and Hash algorithm.
  3. Validate that unsignedSpendTx is spending from the correct Bitcoin address.
  4. Validate that the last transaction output of unsignedSpendTx is [OP_RETURN, OP_PUSHBYTES32, <hashBytes>].

6.5.2.4 Finalize

Given:

  • beaconAnnouncementMap - required, Beacon Announcement Map created as above
  • unnormalizedBeaconAnnouncementMap - required, as above
  • psbts - required, partially signed Bitcoin transactions
  • cas - optional, one of:
    • “ipfs”

Spend the transaction and publish to CAS:

  1. If cas is defined and is not a valid value per above, raise InvalidParameter error.
  2. Set spendTx to the aggregation of the partially signed Bitcoin transactions psbts into a single transaction.
  3. Broadcast spendTx on the Bitcoin network.
  4. If cas is defined:
    1. Publish JSON representation of beaconAnnouncementMap to the CAS network defined by cas.
    2. For each did with a btc1Update in unnormalizedBeaconAnnouncementMap, publish btc1Update to the CAS network defined by cas.

6.6 SMT Beacon

An SMT Beacon creates a Beacon Signal that commits to multiple BTC1 Update Announcements, each identified by a did:btc1 identifier. To do so, it constructs an optimized sparse Merkle tree as defined in Appendix - Optimized Sparse Merkle Tree Implementation and publishes the Merkle root.

An SMT Beacon provides maximum privacy for the DID controller, as the DID controller never has to reveal their DIDs or BTC1 Updates to the aggregator. This introduces a small risk, as the DID controller is not required to prove control over a DID in order to participate.

The beaconType of the service for an SMT Beacon is “SMTBeacon”.

6.6.1 Create Beacon Cohort

Creating a Beacon Cohort requires that the Beacon Aggregator define the conditions for it, advertise it, and accept enrolment by Beacon Participants. The process flow involves the exchange of data between the Beacon Aggregator and Beacon Participants, most notably the DIDs or indexes (hashes of DIDs) involved and the public keys for creating the n-of-n MuSig2 Bitcoin address. A detailed specification is out of scope, but most implementations operate as shown below.

6.6.2 Construct and Send Beacon Signal

Constructing and sending an SMT Beacon signal operates roughly as follows:

6.6.2.1 Construct Unsigned Beacon Signal

Given:

  • network - required, one of:
    • “bitcoin”
    • “signet”
    • “regtest”
    • “testnet3”
    • “testnet4”
    • “mutinynet”
    • number
  • serviceEndpoint - required, a Bitcoin address represented as a URI
  • hashBytes - required, root hash of optimized SMT

Construct a Bitcoin transaction that spends from the Beacon address on the selected network:

  1. If network is not a valid value per above, raise InvalidParameter error.
  2. if network is a number and is outside the range of 1-4, raise InvalidParameter error.
  3. Set bitcoinAddress to the decoding of serviceEndpoint following BIP21.
  4. Ensure bitcoinAddress is funded; if not, fund this address.
  5. Initialize unsignedSpendTx to a Bitcoin transaction that spends a transaction controlled by the bitcoinAddress and contains at least one transaction output. This signal output MUST have the format [OP_RETURN, OP_PUSHBYTES32, <hashBytes>]. If the transaction contains multiple transaction outputs, the signal output MUST be the last transaction output.

6.6.2.2 Construct Proof Path

Given:

  • smt - required, optimized SMT
  • index - required, index provided by DID controller

Calculate the path to the root for the index:

  1. Set path to empty array.
  2. Set node to leaf node for index.
  3. While node is not root of smt:
    1. Set parentNode to parent of node.
    2. If node is left of parentNode, add {right: <rightHashString>}to path, where rightHashString is the hexadecimal string representation of the value at parentNode.rightNode.
    3. If node is right of parentNode, add {left: <leftHashString>} to path, where leftHashString is the hexadecimal string representation of the value at parentNode.leftNode.
  4. Return path.

6.6.2.3 Validate Proof Paths Map and Unsigned Beacon Signal

Given:

  • pathsMap - required, proof paths map constructed as above
  • unsignedSpendTx - required, unsigned Beacon signal constructed as above

Validate the proof paths map and the unsigned Beacon signal:

  1. Validate that unsignedSpendTx is spending from the correct Bitcoin address.
  2. For each did expected to be in the Beacon Signal:
    1. Set index to hash(did).
    2. Set path to the value at index and remove it from the map.
    3. If path is undefined, raise InvalidParameter error.
    4. Extract the current nonce and btc1Update for did from local storage.
    5. If btc1Update is defined, set btc1UpdateAnnouncement to the result of passing btc1Update to the JSON Canonicalization and Hash algorithm and set hashBytes to hash(index + hash(nonce ^ btc1UpdateAnnouncement)), otherwise set hashBytes to hash(index + hash(nonce)).
    6. For each step in path:
      1. Validate that step has a single key-value pair.
      2. Extract key and value from step.
      3. If key is "left", set hashBytes to hash(value + hashBytes); otherwise, if key is "right", set hashBytes to hash(hashBytes + value); otherwise, raise InvalidParameter error.
    7. Validate that the last transaction output of unsignedSpendTx is [OP_RETURN, OP_PUSHBYTES32, <hashBytes>].
    8. If btc1UpdateAnnouncement is defined, construct as smtProof the object {id: <hashString>, nonce: <nonce>, updateId: <btc1UpdateHashString>, path: <path>}, otherwise construct as smtProof the object {id: <hashString>, nonce: <nonce>, path: <path>}, where hashString is the hexadecimal string representation of hashBytes and btc1UpdateHashString is the hexadecimal string representation of btc1UpdateAnnouncement.
    9. Store smtProof for later presentation to verifiers.
  3. If pathsMap is not empty, raise InvalidParameter error.

6.6.2.4 Finalize

Given:

  • psbts - required, partially signed Bitcoin transactions

Spend the transaction:

  1. Set spendTx to the aggregation of the partially signed Bitcoin transactions psbts into a single transaction.
  2. Broadcast spendTx on the Bitcoin network.

6.7 Processing Signals

Given:

  • did - required, the did:btc1 identifier whose signals are to be processed
  • sidecarDocuments - required, array of documents required for resolution not stored on a CAS,including:
  • smtProofs - required for services of beaconType “SMTBeacon”, array of SMT proofs of inclusion or non-inclusion
  • cas - optional, one of:
    • “ipfs”
  • targetVersionId - optional, DID document version ID required
  • targetVersionTime - optional, DID document version time required

Process the Beacon Signals to reconstruct the DID document:

  1. If cas is defined and is not a valid value per above, raise InvalidParameter error.
  2. Set sidecarDocumentsMap to empty map.
  3. For each sidecarDocument in sidecarDocuments:
    1. Set hashBytes to the result of passing sidecarDocument to the JSON Canonicalization and Hash algorithm.
    2. Set id to the hexadecimal string representation of hashBytes.
    3. Add id and sidecarDocument to sidecarDocumentsMap.
  4. Set smtProofsMap to empty map.
  5. For each smtProof in smtProofs:
    1. Add smtProof.id and smtProof to smtProofsMap.
  6. If did was constructed with idType “key”, set didDocument to the deterministically generated initial document.
  7. If did was constructed with idType “external”:
    1. Extract genesisBytes from did.
    2. Set id to the hexadecimal string representation of genesisBytes.
    3. Get didDocument from sidecarDocumentsMap by its id if available, or from CAS by its id if not and cas is defined.
    4. Update placeholder values in didDocument with did as required.
    5. If didDocument is not a valid DID document, raise InvalidDidUpdate error.
  8. Until terminated:
    1. Set btc1Update to null.
    2. For each service in didDocument.service where service.type is “BTC1Beacon”:
      1. Get the next transaction from the Bitcoin address at service.serviceEndpoint.
      2. If targetVersionTime is defined and is less than the transaction time, skip to next service.
      3. If the last output transaction is not of the form [OP_RETURN, OP_PUSHBYTES32, <hashBytes>], skip to next service.
      4. Extract hashBytes from the last output transaction.
      5. If service.beaconType is not “SingletonBeacon”, “MapBeacon”, or “SMTBeacon”, raise InvalidParameter error.
      6. If service.beaconType is “SingletonBeacon”, set tempBtc1Update to the result of Process Singleton Beacon Signal.
      7. If service.beaconType is “MapBeacon”, set tempBtc1Update to the result of Process Map Beacon Signal.
      8. If service.beaconType is “SMTBeacon”, set tempBtc1Update to the result of Process SMT Beacon Signal.
      9. If tempBtc1Update is null, skip to next service.
      10. Set tempDidDocument to transformation of didDocument with tempBtc1Update.
      11. If tempDidDocument.versionIddidDocument.versionId + 1, skip to next service.
      12. If btc1Update is not null and tempBtc1Updatebtc1Update, raise InvalidDidUpdate error.
      13. Set btc1Update to tempBtc1Update.
    3. If btc1Update is null, terminate.
    4. Set didDocument to transformation of didDocument with btc1Update.
    5. If didDocument is not a valid DID document, raise InvalidDidUpdate error.
    6. If didDocument.id is not the same as the did:btc1 identifier, raise InvalidDidUpdate error.
    7. If didDocument has no BTC1 Beacon service types (i.e., no services where service.type is “BTC1Beacon”), raise InvalidDidUpdate error.
    8. If targetVersionId is defined and didDocument.versionId = targetVersionId, terminate.
  9. If targetVersionId is defined and didDocument.versionIdtargetVersionId, raise InvalidDidUpdate error.
  10. If targetVersionId is not defined: 1. For each service in didDocument.service where service.type is “BTC1Beacon”:
    1. Get the next transaction from the Bitcoin address at service.serviceEndpoint.
    2. If targetVersionTime is defined and is less than the transaction time, skip to next service.
    3. If the last output transaction is of the form [OP_RETURN, OP_PUSHBYTES32, <hashBytes>], raise InvalidDidUpdate error.
  11. Return didDocument.

6.7.1 Process Singleton Beacon Signal

  1. Set id to the hexadecimal string representation of hashBytes.
  2. Get btc1Update from sidecarDocumentsMap by its id if available, or from CAS by its id if not and cas is defined.
  3. If btc1Update is undefined, raise InvalidDidUpdate error.
  4. Set btc1Update
  5. Return btc1Update.

NOTE: The act of retrieving from sidecarDocumentsMap or CAS validates the document hash.

6.7.2 Process Map Beacon Signal

  1. Set id to the hexadecimal string representation of hashBytes.
  2. Get map from sidecarDocumentsMap by its id if available, or from CAS by its id if not and cas is defined.
  3. If map is undefined, raise InvalidDidUpdate error.
  4. Set updateId to the value of map.<did>.
  5. If updateId is undefined, return null.
  6. Get btc1Update from sidecarDocumentsMap by its updateId if available, or from CAS by its updateId if not and cas is defined.
  7. If btc1Update is undefined, raise InvalidDidUpdate error.
  8. Return btc1Update.

NOTE: The act of retrieving from sidecarDocumentsMap or CAS validates the document hash.

6.7.3 Process SMT Beacon Signal

  1. Set id to the hexadecimal string representation of hashBytes.
  2. Get smtProof from smtProofsMap by its id.
  3. If smtProof is undefined, raise InvalidDidUpdate error.
  4. Set index to hash(did).
  5. Set nonce to the value of smtProof.nonce.
  6. Set updateId to the value of smtProof.updateId.
  7. If updateId is defined, set btc1UpdateAnnouncement to the binary representation of updateId and set verifyHashBytes to hash(index + hash(nonce ^ btc1UpdateAnnouncement)), otherwise set verifyHashBytes to hash(index + hash(nonce)).
  8. For each step in smtProof.path:
    1. Validate that step has a single key-value pair.
    2. Extract key and value from step.
    3. If key is "left", set verifyHashBytes to hash(value + verifyHashBytes); otherwise, if key is "right", set verifyHashBytes to hash(verifyHashBytes + value); otherwise, raise InvalidDidUpdate error.
  9. If verifyHashByteshashBytes, raise InvalidDidUpdate error.
  10. If updateId is undefined, return null.
  11. Get btc1Update from sidecarDocumentsMap by its updateId if available, or from CAS by its updateId if not and cas is defined.
  12. If btc1Update is undefined, raise InvalidDidUpdate error.
  13. Return btc1Update.

NOTE: The act of retrieving from sidecarDocumentsMap validates the document hash.

7 CRUD Operations

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

7.1 Create

The Create operation consists of two main algorithms for creating identifiers and DID documents. A did:btc1 identifier and DID document can either be created from a deterministic key pair or from an external intermediate DID document. 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.

7.1.1 From Deterministic Key Pair

The From Deterministic Key Pair algorithm encodes a secp256k1 public key as a did:btc1 identifier. The public key is then used to deterministically generate the initial DID document.

It takes the following inputs:

  • pubKeyBytes - a compressed SEC encoded secp256k1 public key; REQUIRED; bytes
  • version - the identifier version; OPTIONAL; integer; default=1.
  • network - the Bitcoin network used for the identifier; OPTIONAL; string; default="bitcoin".

It returns the following outputs:

  • did - a newly created did:btc1 identifier; string
  • initialDocument - the valid first version of a DID document for a given did:btc1 identifier.

The steps are as follows:

  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.

7.1.2 From External Intermediate DID Document

The From External intermediate DID document algorithm enables the ability to create a did:btc1 from an external intermediate DID document. This allows for a more complex initial DID document, including the ability to include Service Endpoints and BTC1 Beacons that support aggregation.

It takes the following inputs:

  • intermediateDocument - any arbitrary, valid DID document with the identifier replaced with the placeholder value throughout all fields (e.g. the id field) did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. It SHOULD include at least one verificationMethod and service of the type SingletonBeacon; REQUIRED; object.
  • version - the identifier version; OPTIONAL; integer; default=1.
  • network - the Bitcoin network where the DID and DID document can be resolved; OPTIONAL; string; default="bitcoin".

It returns the following outputs:

  • did - a newly created did:btc1 identifier; string
  • initialDocument - the valid first version of a DID document for a given btc1 identifier.

The steps are as follows:

  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 the InterPlanetary File System (IPFS). If doing so, implementations MUST use Content Identifiers (CIDs) generated following the IPFS v1 algorithm.
  10. Return did and initialDocument.

7.2 Read

The Read operation is an algorithm consisting of a series of subroutine algorithms executed by a resolver after a resolution request identifying a specific did:btc1 identifier is received from a client at Resolution Time. The request MUST always contain the resolutionOptions object containing additional information to be used in resolution. This object MAY be empty. See the DID Resolution specification for further details about the DID Resolution Options object. 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.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • resolutionOptions - an object that extends the default options per the DID Resolution specification; the below list is not intended to be exhaustive; OPTIONAL; object
    • versionId - the version of the DID and DID document, an incrementing integer starting from 1; OPTIONAL; integer
    • versionTime - a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integer
    • sidecarData - data necessary for resolving a DID such as BTC1 Updates and SMT proofs; OPTIONAL; object.
    • network - the Bitcoin network used for resolution; OPTIONAL; string; default="bitcoin"

It returns the following output:

  • targetDocument - a DID Core conformant DID document after all updates have been found, validated and applied; object

The steps are as follows:

  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 DID 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.

7.2.1 Resolve Initial DID Document

This algorithm specifies how to resolve an initial DID document and validate it against the identifier for a specific did:btc1.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • identifierComponents - The decoded parts of a did:btc1 identifier; REQUIRED; object
    • idType - the type of identifier (KEY or EXTERNAL); REQUIRED; string
    • version - the identifier version; REQUIRED; integer
    • network - the Bitcoin network used for the identifier; REQUIRED; string
    • genesisBytes - the originating public key; REQUIRED; bytes
  • resolutionOptions - options that extends the default options per the DID Resolution specification; OPTIONAL; object
    • versionId - the version of the DID and DID document, an incrementing integer starting from 1; the below list is not intended to be exhaustive; OPTIONAL; integer
    • versionTime - a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integer
    • sidecarData - data necessary for resolving a DID such as BTC1 Updates and SMT proofs.; OPTIONAL; object
    • network - the Bitcoin network used for resolution; OPTIONAL; string; default="bitcoin"

It returns the following output:

  • initialDocument - the valid first version of a DID document for a given btc1 identifier.

The steps are as follows:

  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.

7.2.1.1 Deterministically Generate Initial DID Document

The Deterministically Generate initial DID document algorithm generates an initial DID document from a secp256k1 public key.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • identifierComponents - The decoded parts of a did:btc1 identifier; REQUIRED; object
    • idType - the type of identifier (KEY or EXTERNAL); REQUIRED; string
    • version - the identifier version; REQUIRED; integer
    • network - the Bitcoin network used for the identifier; REQUIRED; string
    • genesisBytes - the originating public key; REQUIRED; bytes

It returns the following output:

  • initialDocument - the valid first version of a DID document for a given did:btc1** identifier.

The steps are as follows:

  1. Set keyBytes to identifierComponents.genesisBytes.
  2. Initialize an 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 v1.1 context “https://www.w3.org/ns/did/v1.1”.
    2. Append the did:btc1 context “https://btc1.dev/context/v1”.
    3. Set initialDocument['@context]' to contextArray.
  5. Initialize a controllerArray to empty array:
    1. Append the identifier.
    2. Set initialDocument.controller to controllerArray.
  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 encoding algorithm in BIP340 Multikey.
  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.
7.2.1.1.1 Deterministically Generate Beacon Services

The Deterministically Generate Beacon Services algorithm generates three BTC1 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 private key associated with the keyBytes. Each BTC1 Beacon is a Singleton Beacon.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • keyBytes - a compressed SEC encoded secp256k1 public key; REQUIRED; bytes
  • network - the Bitcoin network used for the identifier; REQUIRED; string; default="bitcoin"

It returns the following output:

  • services - an array of BTC1 Beacon service objects containing the following properties
    • type - described the kind of service being defined used to determine how to produce updates; REQUIRED; string; default="SingletonBeacon"
    • id - the did:btc1 identifier controlling the beacon including the DID fragment pointing to the location of a key in the DID document; REQUIRED; string
    • serviceEndpoint - a P2PKH, P2WPKH or P2TR bitcoin address where beacon signals can be broadcast; REQUIRED; string

Below is an example of a returning object:

[
    {
        "type": "SingletonBeacon",
        "id": "did:btc1:k1qgpgwtp2dpe3thqny6jngl5eg6p4wghd04yj70jcp8qe4nh75hd4dhc8f08q4#initialP2PKH",
        "serviceEndpoint": "bitcoin:mppdEp4wznKcUkDrw7LhrtKpTFx19vXxi8"
    },
    {
        "type": "SingletonBeacon",
        "id": "did:btc1:k1qgpgwtp2dpe3thqny6jngl5eg6p4wghd04yj70jcp8qe4nh75hd4dhc8f08q4#initialP2WPKH",
        "serviceEndpoint": "bitcoin:bcrt1qvcgtvk08lx52apzxxd0c663l6274u4muchq7qg"
    },
    {
        "type": "SingletonBeacon",
        "id": "did:btc1:k1qgpgwtp2dpe3thqny6jngl5eg6p4wghd04yj70jcp8qe4nh75hd4dhc8f08q4#initialP2TR",
        "serviceEndpoint": "bitcoin:bcrt1put79jyupjf66qap9n089m45qwhd53sqmkej66kf5rsxudajsf6dstl5jqw"
    }
]

The steps are as follows:

  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.

7.2.1.2 External Resolution

The External Resolution algorithm externally retrieves an intermediate DID document as an intermediateDocumentRepresentation, either by retrieving it from Content Addressable Storage (CAS) or from the Sidecar Data provided as part of the resolution request.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • identifierComponents - The decoded parts of a did:btc1 identifier; REQUIRED; object
    • idType - the type of identifier (KEY or EXTERNAL); REQUIRED; string
    • version - the identifier version; REQUIRED; integer
    • network - the Bitcoin network used for the identifier; REQUIRED; string
    • genesisBytes - the originating intermediate DID document; REQUIRED; bytes
  • resolutionOptions - options that extends the default options per the DID Resolution specification; the below list is not intended to be exhaustive; OPTIONAL; object
    • versionId - the version of the DID and DID document, an incrementing integer starting from 1; OPTIONAL; integer
    • versionTime - a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integer
    • sidecarData - data necessary for resolving a DID such as BTC1 Updates and SMT proofs.; OPTIONAL; object
    • network - the Bitcoin network used for resolution; OPTIONAL; string; default="bitcoin"

It returns the following output:

The steps are as follows:

  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 DID 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.
7.2.1.2.1 Sidecar Initial DID Document Validation

The Sidecar Initial DID Document Validation algorithm validates an initialDocument against its identifier, by first constructing the intermediate DID document representation and verifying that the hash of the intermediateDocumentRepresentation matches the bytes encoded within the identifier.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • identifierComponents - The decoded parts of a did:btc1 identifier; REQUIRED; object
    • idType - the type of identifier (KEY or EXTERNAL); REQUIRED; string
    • version - the identifier version; REQUIRED; integer
    • network - the Bitcoin network used for the identifier; REQUIRED; string
    • genesisBytes - the originating intermediate DID document; REQUIRED; bytes
  • initialDocument - the initial DID document for the given identifier.

It returns the following outputs or throws an error:

The steps are as follows:

  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.
7.2.1.2.2 CAS Retrieval

The CAS Retrieval algorithm attempts to retrieve an initial DID document from a Content Addressable Storage (CAS) system by converting the bytes in the identifier into a Content Identifier (CID).

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • identifierComponents - The decoded parts of a did:btc1 identifier; REQUIRED; object
    • idType - the type of identifier (KEY or EXTERNAL); REQUIRED; string
    • version - the identifier version; REQUIRED; integer
    • network - the Bitcoin network used for the identifier; REQUIRED; string
    • genesisBytes - the originating intermediate DID document; REQUIRED; bytes

It returns the following output:

The steps are as follows:

  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.

7.2.2 Resolve Target Document

The Resolve Target Document algorithm resolves a DID document from an initial document by walking the Bitcoin blockchain to identify Beacon Signals that announce BTC1 Updates applicable to the did:btc1 identifier being resolved.

It takes the following inputs:

  • initialDocument - the initial DID document that was used to initiate the did:btc1 identifier being resolved as verified by the Resolve Initial DID Document algorithm. A DID Core conformant DID document; REQUIRED; object
  • resolutionOptions - options that extends the default options per the DID Resolution specification; the below list is not intended to be exhaustive; OPTIONAL; object
    • versionId - the version of the DID and DID document, an incrementing integer starting from 1; OPTIONAL; integer
    • versionTime - a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integer
    • sidecarData - data necessary for resolving a DID such as BTC1 Updates and SMT proofs.; OPTIONAL; object
    • network - the Bitcoin network used for resolution; OPTIONAL; string; default="bitcoin"

It returns the following output or throws an error:

  • targetDocument - a DID Core conformant DID document after all updates have been found, validated and applied; object

The steps are as follows:

  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 btc1UpdateHashHistory to an empty array.
  8. Set didDocumentHistory to an array containing the initialDocument.
  9. Set contemporaryBlockheight to 0.
  10. Set contemporaryDIDDocument to the initialDocument.
  11. Set targetDocument to the result of calling the Traverse Bitcoin Blockchain History algorithm passing in contemporaryDIDDocument, contemporaryBlockheight, currentVersionId, targetVersionId, targetTime, didDocumentHistory, btc1UpdateHashHistory, signalsMetadata, and network.
  12. Return targetDocument.

7.2.2.1 Traverse Bitcoin Blockchain History

The Traverse Bitcoin Blockchain History algorithm traverses this history of the Bitcoin blockchain, starting from the block with the blockheight equal to contemporaryBlockheight, to find beaconSignals emitted by BTC1 Beacons specified within the contemporaryDIDDocument. Each beaconSignal is processed to retrieve a BTC1 Update 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.

It 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; REQUIRED; object.
  • 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; REQUIRED; integer.
  • currentVersionId - the version of the contemporary DID document. An integer starting from 1 and incrementing by 1 with each BTC1 Update applied to the DID document; REQUIRED; integer.
  • targetVersionId - the version of the DID document that the resolution algorithm is attempting to resolve; OPTIONAL; integer.
  • 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; REQUIRED; integer.
  • didDocumentHistory - an ordered array of DID documents from version 1 up to the current version; REQUIRED; array.
  • btc1UpdateHashHistory - an ordered array of SHA256 hashes of BTC1 Updates that have been applied to the DID document by the resolution algorithm in order to construct the contemporaryDIDDocument; REQUIRED; array.
  • signalsMetadata - a Map from Bitcoin transaction identifiers of Beacon Signals to an object containing Sidecar Data for that signal provided as part of the resolutionOptions; REQUIRED; Map containing the following properties:
    • btc1Update - a BTC1 Update which SHOULD match the update announced by the Beacon Signal. In the case of a SMT proof of non-inclusion, no BTC1 Update MAY be provided; OPTIONAL; object
    • proofs - a Sparse Merkle Tree proof that the provided btc1Update value is the value at the leaf indexed by the did:btc1 being resolved. REQUIRED; TODO: What exactly this structure is needs to be defined.
  • network - a string identifying the Bitcoin network of the did:btc1 identifier. The algorithm MUST query the Bitcoin blockchain identified by the network; REQUIRED; string

It returns the following output once either targetTime or targetVersionId have been reached:

  • contemporaryDIDDocument - A contemporary DID document conformant to DID Core resolved using updates found on-chain from genesis to contemporaryBlockheight.

The steps are as follows:

  1. Set contemporaryHash to the result of passing contemporaryDIDDocument into the JSON Canonicalization and Hash algorithm.
  2. Find all BTC1 Beacons in contemporaryDIDDocument.service where service.type equals one of SingletonBeacon, CIDAggregateBeacon and SMTAggregateBeacon.
  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, push contemporaryHash into the btc1UpdateHashHistory list and run the Confirm Duplicate Update algorithm passing in btc1Update and btc1UpdateHashHistory.
    2. If update.targetVersionId equals currentVersionId + 1:
      1. Check that the base58 decoding of 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. Push contemporaryDIDDocument onto didDocumentHistory.
      4. Increment currentVersionId.
      5. Set unsecuredUpdate to a copy of the update object.
      6. Remove the proof property from the unsecuredUpdate object.
      7. Set updateHash to the result of passing unsecuredUpdate into the JSON Canonicalization and Hash algorithm.
      8. Push updateHash onto btc1UpdateHashHistory.
      9. 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 Bitcoin Blockchain History algorithm passing in contemporaryDIDDocument, contemporaryBlockheight, currentVersionId, targetVersionId, targetTime, didDocumentHistory, btc1UpdateHashHistory, signalsMetadata, and network.
  13. If targetVersionId in not null, set targetDocument to the index at the targetVersionId of the didDocumentHistory array.
  14. Return targetDocument.

7.2.2.2 Find Next Signals

The Find Next Signals algorithm 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 to query the Bitcoin blockchain:

It takes the following inputs:

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

It returns the following output:

  • nextSignals - an array of signal objects with the following properties:
    • beaconId - The id for the BTC1 Beacon that the Beacon Signal was announced by.
    • beaconType - The type of the BTC1 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.

The steps are as follows:

  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 last transaction output is of the format [OP_RETURN, OP_PUSHBYTES32, <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.

7.2.2.3 Process Beacon Signals

The Process Beacon Signals algorithm processes each Beacon Signal by attempting to retrieve a BTC1 Update Announcement for a specific did:btc1 identifier. Then the algorithm retrieves and validates the BTC1 Update committed to by the announcement. The Beacon Type of the BTC1 Beacon that broadcast the Beacon Signal defines the algorithms to retrieve and validate BTC1 Update Announcements and their associated BTC1 Updates against the Beacon Signal.

It takes the following inputs:

  • beaconSignals - an array of struct representing Beacon Signals retrieved through executing the Find Next Signals algorithm; REQUIRED; object containing the follow properties:
    • beaconId - the id for the BTC1 Beacon that the signal was announced by; REQUIRED; string.
    • beaconType - the type of the BTC1 Beacon that announced the signal; REQUIRED; string.
    • tx - the Bitcoin transaction that is the Beacon Signal; REQUIRED; string.
  • 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; REQUIRED; object containing the following properties:
    • btc1Update - a BTC1 Update which SHOULD match the update announced by the Beacon Signal. In the case of a SMT proof of non-inclusion, no BTC1 Update MAY be provided; OPTIONAL; object.
    • proofs - a Sparse Merkle Tree proof that the provided btc1Update value is the value at the leaf indexed by the did:btc1 being resolved; OPTIONAL; object.

It returns the following output:

The steps are as follows:

  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 btc1Update to null.
    6. If type == SingletonBeacon:
      1. Set btc1Update to the result of passing signalTx and signalSidecarData to the Process Singleton Beacon Signal algorithm.
    7. If type == CIDAggregateBeacon:
      1. Set btc1Update to the result of passing signalTx and signalSidecarData to the [Process CIDAggregate Beacon Signal] algorithm.
    8. If type == SMTAggregateBeacon:
      1. Set btc1Update to the result of passing signalTx and signalSidecarData to the [Process SMTAggregate Beacon Signal] algorithm.
    9. If btc1Update is not null, push btc1Update to updates.
  3. Return updates.

7.2.2.4 Confirm Duplicate Update

The Confirm Duplicate Update algorithm verifies that a BTC1 Update is a duplicate against the hash history of previously applied updates.

It takes the following inputs:

  • btc1Update - the unsecured BTC1 Update to confirm as a duplicate or not; REQUIRED; object.
  • btc1UpdateHashHistory - an array of hashes corresponding to each BTC1 Update; REQUIRED; array.

It returns successfully if the update is a duplicate else it throws an error.

The steps are as follows:

  1. Let unsecuredUpdate be a copy of the update object.
  2. Remove the proof property from the unsecuredUpdate object.
  3. Let updateHash equal the result of passing unsecuredUpdate into the JSON Canonicalization and Hash algorithm.
  4. Let updateHashIndex equal update.targetVersionId - 2.
  5. Let historicalUpdateHash equal updateHashHistory[updateHashIndex].
  6. Assert historicalUpdateHash equals updateHash, if not MUST throw a LatePublishing error.
  7. Return

7.2.2.5 Apply DID Update

The Apply DID Update 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.

It 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; REQUIRED; object.
  • update - the BTC1 Update to apply to the contemporaryDIDDocument; REQUIRED; object.

It returns the following output:

The steps are as follows:

  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"
    }
  5. Set expectedProofPurpose to “capabilityInvocation”.

  6. Set mediaType to “application/ld+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 Verifiable Credentials (VC) Data Integrity specification.

  9. If verificationResult.verified equals False, MUST raise an 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.

7.3 Update

The Update algorithm calls a series of subroutines to construst, invoke and announce BTC1 Updates for did:btc1 identifiers and their corresponding DID documents. 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 Data Integrity Cryptosuite with a proofPurpose of capabilityInvocation.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string.
  • sourceDocument - the DID document being updated; REQUIRED; object.
  • sourceVersionId - the version of the DID and DID document, an incrementing integer starting from 1; REQUIRED; integer.
  • documentPatch - 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; REQUIRED; object.
  • verificationMethodId - identifier for a verificationMethod within the sourceDocument. The verificationMethod identified MUST be a BIP340 Multikey; REQUIRED; string.
  • beaconIds - an array containing the IDs that correspond to BTC1 Beacons in the DID document. These MUST identify service endpoints with one of the three Beacon Types SingletonBeacon, MapBeacon, and SMTBeacon; REQUIRED; array.

It returns the following output:

  • 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; Object containing the following properties:
    • btc1Update - A BTC1 Update which SHOULD match the update announced by the Beacon Signal. In the case of a SMT proof of non-inclusion, the btc1Update will be null; object.
    • proofs - A Sparse Merkle Tree proof that the provided btc1Update value is the value at the leaf indexed by the did:btc1 being resolved; array.

The steps are as follows:

  1. Set unsecuredUpdate to the result of passing btc1Identifier, sourceDocument, sourceVersionId, and documentPatch into the Construct BTC1 Update 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] == zQ3s
  4. Set unsecuredBtc1Update to the result of passing btc1Identifier, unsecuredUpdate, andverificationMethod` to the Invoke BTC1 Update algorithm.
  5. Set signalsMetadata to the result of passing btc1Identifier, sourceDocument, beaconIds and unsecuredBtc1Update to the Announce DID Update algorithm.
  6. Return signalsMetadata. It is up to implementations to ensure that the signalsMetadata is persisted.

7.3.1 Construct BTC1 Update

The Construct BTC1 Update algorithm applies a documentPatch to a sourceDocument and verifies the resulting targetDocument is a conformant DID document.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string
  • sourceDocument - the DID document being transformed by the documentPatch; REQUIRED; object.
  • sourceVersionId - the version of the DID and DID document, an incrementing integer starting from 1; REQUIRED; integer.
  • documentPatch - 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; REQUIRED; object.

It returns the following output:

  • unsecuredBtc1Update - a newly created BTC1 Update; object

The steps are as follows:

  1. Check that sourceDocument.id equals btc1Identifier else MUST raise invalidDidUpdate error.
  2. Initialize unsecuredBtc1Update to an empty object.
  3. Set unsecuredBtc1Update.@context to the following list. ["https://w3id.org/zcap/v1", "https://w3id.org/security/data-integrity/v2", "https://w3id.org/json-ld-patch/v1", "https://btc1.dev/context/v1"]
  4. Set unsecuredBtc1Update.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 unsecuredBtc1Update.sourceHash to the base64 of sourceHashBytes.
  9. Set targetHashBytes to the result of passing targetDocument into the JSON Canonicalization and Hash algorithm.
  10. Set unsecuredBtc1Update.targetHash to the base64 of targetHashBytes.
  11. Set unsecuredBtc1Update.targetVersionId to sourceVersionId + 1
  12. Return unsecuredBtc1Update.

7.3.2 Invoke BTC1 Update

The Invoke BTC1 Update algorithm etrieves the privateKeyBytes for the verificationMethod and adds a capability invocation in the form of a Data Integrity proof following the ZCAP-LD and Verifiable Credentials (VC) Data Integrity specifications.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string.
  • unsecuredBtc1Update - an unsecured BTC1 Update; REQUIRED; object.
  • verificationMethod - an object containing reference to keys and/or Beacons to use for ZCAP-LD; REQUIRED; string.

It returns the following output:

  • btc1Update - a BTC1 Update object invoking the capability to update a specific did:btc1 DID document.

The steps are as follows:

  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.
  10. Set cryptosuite to the result of executing the Cryptosuite Instantiation algorithm from the BIP340 Data Integrity specification passing in proofOptions.
  11. Set btc1Update to the result of executing the Add Proof algorithm from VC Data Integrity passing unsecuredBtc1Update as the input document, cryptosuite, and the set of proofOptions.
  12. Return btc1Update.

7.3.3 Announce DID Update

The Announce DID Update algorithm retrieves beaconServices from the sourceDocument and calls the Broadcast DID Update algorithm corresponding to the type of the BTC1 Beacon.

It takes the following inputs:

  • identifier - a valid did:btc1 identifier; REQUIRED; string.
  • sourceDocument - the DID document being updated; REQUIRED; object.
  • beaconIds - an array containing the IDs that correspond to BTC1 Beacons in the DID document. These MUST identify service endpoints with one of the three Beacon Types SingletonBeacon, MapBeacon, and SMTBeacon; REQUIRED; array.
  • btc1Update - a BTC1 Update object invoking the capability to update a specific did:btc1 DID document; REQUIRED; object.

It returns the following output:

  • signalsMetadata - a mapping from Bitcoin transaction identifiers of Beacon Signals to a struct containing Sidecar Data for that signal, provided as part of the resolutionOptions; a map with the following properties:
    • btc1Update - a BTC1 Update which SHOULD match the update announced by the Beacon Signal. In the case of a SMT proof of non-inclusion, the btc1Update will be null; object.
    • proofs - A Sparse Merkle Tree proof that the provided btc1Update value is the value at the leaf indexed by the did:btc1 being resolved; object.

The steps are as follows:

  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 btc1Update to the [Broadcast Singleton Beacon Signal] algorithm.
    3. Else If beaconService.type == CIDAggregateBeacon:
      1. Set signalMetadata to the result of passing btc1Identifier, beaconService and btc1Update to the [Broadcast CIDAggregate Beacon Signal] algorithm.
    4. Else If beaconService.type == SMTAggregateBeacon:
      1. Set signalMetadata to the result of passing btc1Identifier, beaconService and btc1Update to the [Broadcast SMTAggregate Beacon Signal] algorithm.
    5. Else:
      1. MUST throw invalidBeacon error.
  5. Merge signalMetadata into signalsMetadata.
  6. Return signalsMetadata

7.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 BTC1 Update with a JSON patch that adds this property and announces the BTC1 Update by broadcasting an Authorized Beacon Signal following the algorithm in Update. Once a did:btc1 has been deactivated this state is considered permanent and resolution MUST terminate.

8 Security Considerations

8.1 did:btc1 Design Considerations

8.1.1 Late Publishing

did:btc1 was designed to avoid Late Publishing such that, independent of when a resolution occurs, the Decentralized Identifier (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.

Additionally, when resolvers are passed resolution options specifying a versionId they MUST process the full history of the signals up to the current time in order to check for late publishing. Any update that specifies the same versionId but contains different update operations MUST trigger a Late Publishing error. This is not the case for versionTime. When a resolver is passed a versionTime option then the state of the DID document can be returned once the all the signals in the blocks before this timestamp have been processed.

8.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.

8.2 Considerations Deploying did:btc1

8.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 BTC1 Updates 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.

8.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 BTC1 Beacon address is an n-of-n and that one of the n keys is the cohort key provided to the BTC1 Beacon coordinator. This can be achieved only by constructing the address for themselves from the set of cohort keys which the coordinator SHOULD provide.

8.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.

8.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 BTC1 Beacon that replaces key material and BTC1 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 BTC1 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.

8.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 BTC1 Updates. Should vulnerabilities be discovered in this scheme or if advancements in quantum computing compromise its cryptographic foundations, the did:btc1 method would become obsolete.

8.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.

9 Privacy Considerations

9.1 did:btc1 Design Considerations

9.1.1 Updates Need not be Public

did:btc1 was designed such that updates to Decentralized Identifier (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.

9.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 the InterPlanetary File System (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.

9.1.3 Offline DID Creation

did:btc1 was designed to support Offline Creation, that is, the generation 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.

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

BTC1 Beacon coordinators in did:btc1 are entities that coordinate Aggregate Beacons and the corresponding Beacon Signals that announce and anchor an aggregated set of BTC1 Updates. 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 Content Identifier (CID) of the update for a specific DID which they include in the Beacon Signal according to the type of the BTC1 Beacon.

9.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.

9.2 Considerations Deploying did:btc1

9.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 BTC1 Updates, including the updates posted by that DID’s BTC1 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.

9.2.2 Beacon Coordinators Know the DIDs Being Aggregated by a Cohort

Within Sparse Merkle Tree (SMT) BTC1 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 BTC1 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.

9.2.3 CIDAggregate Cohort Members Know All DIDs that are Updated

Cohort members participating in a CIDAggregate BTC1 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 BTC1 Updates. Each DID controller SHOULD independently retrieve and verify the contents of the update bundle to ensure it contains the expected update for their DID.

9.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.

10 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

11 Appendix

11.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.

11.1.1 Bech32m Encoding

Given:

  • hrp - required, a string representing the Human-Readable Part (HRP) 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.

11.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.

11.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.

11.3 Fetch Content from Addressable Storage

A macro function that takes in SHA256 hash of some content, hashBytes, converts these bytes to an InterPlanetary File System (IPFS) v1 Content Identifier (CID) 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

11.4 Root did:btc1 Update Capabilities

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

11.4.1 Derive Root Capability from did:btc1 Identifier

This algorithm deterministically generates an Authorization Capabilities for Linked Data (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"
}

11.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 btc1Update. 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"
  }
}

12 Appendix - Optimized Sparse Merkle Tree Implementation

12.1 Overview

From Wikipedia:

In cryptography and computer science, a hash tree or Merkle tree is a tree in which every “leaf” node is labelled with the cryptographic hash of a data block, and every node that is not a leaf (called a branch, inner node, or inode) is labeled with the cryptographic hash of the labels of its child nodes. A hash tree allows efficient and secure verification of the contents of a large data structure. A hash tree is a generalization of a hash list and a hash chain.

These are the requirements for using Merkle trees to signal commitments in Beacons:

  • Each data block is either a BTC1 Update or null.
  • No key may have more than one data block.
  • The hash of a non-leaf node is the hash of the concatenation of its child nodes’ hashes.
  • The only thing published to Bitcoin is the top hash (the Merkle root).

The DID controller has to prove either inclusion or non-inclusion in the Beacon Signal. To prove inclusion, the DID controller provides either the BTC1 Update (from which the verifier must calculate the hash) or the hash (which the verifier can use to retrieve the BTC1 Update from a CAS); to prove non-inclusion, the DID controller provides the null value (from which the verifier must calculate the hash). In addition, the DID controller must provide the hashes of each peer in the tree (the Merkle proof) as the verifier walks up it to determine the top hash (which, in turn, must have been provided to the DID controller by the aggregator).

Let’s assume that the DID controller has been allocated position 13 (1101).

To prove that the DID is included in the signal, the DID controller provides the BTC1 Update to calculate Hash 1101 and the values Hash 1100, Hash 111, Hash 10, and Hash 0. The verifier then calculates Hash 110, Hash 11, Hash 1, and Top Hash. If that last value matches the value in the signal, the verifier knows that the DID is included in the signal.

The logic is the same for non-inclusion, except that the DID controller provides the null value instead of the BTC1 Update to calculate Hash 1101.

In either case, the DID presentation would include something like the following:

{
  "peers": [
    "<< Hexadecimal of Hash 1100 >>",
    "<< Hexadecimal of Hash 111 >>",
    "<< Hexadecimal of Hash 10 >>",
    "<< Hexadecimal of Hash 0 >>"
  ]
}

This assumes that hash(X + Y) = hash(Y + X), i.e., that the addition operation is commutative. If not, then the position of the peer node must be included:

{
  "peers": [
    {
      "left": "<< Hexadecimal of Hash 1100 >>"
    },
    {
      "right": "<< Hexadecimal of Hash 111 >>"
    },
    {
      "left": "<< Hexadecimal of Hash 10 >>"
    },
    {
      "left": "<< Hexadecimal of Hash 0 >>"
    }
  ]
}

12.2 Attacks

12.2.1 Misrepresented Proof of Inclusion/Non-Inclusion

Let’s assume that a nefarious actor (NA) joined the cohort in the beginning and was allocated position 2 (0010). At some point in time, NA gains access to the cryptographic material and the entire DID history for the DID in position 13 (1101) belonging to a legitimate actor (LA). NA does not gain access to the cryptographic material LA uses to sign their part of the n-of-n P2TR Bitcoin address, which is unrelated to the DID. LA discovers the breach immediately and posts an update, rotating their keys or deactivating the DID.

NA makes a presentation with LA’s DID and, using the Sidecar method, provides all the legitimate DID updates except the most recent one. In its place, NA provides proof of inclusion (to change the DID document) or non-inclusion (to retain the prior version of the DID document), using the material provided by the aggregator for position 2 (0010), for which NA posted an update (for inclusion) or nothing (for non-inclusion). If the direction is not included, there is no way for the verifier to know that the path taken to the root is illegitimate, and it accepts the presentation by NA. If the direction is included, comparison to previous presentations would detect the breach by noting the changes in the direction, assuming that once allocated, the DID position is fixed.

To mitigate this attack, a DID’s position must be fixed deterministically and the hashing operation most not be commutative, i.e., hash(X + Y)hash(Y + X). The following algorithm meets these requirements:

  1. A DID’s position is the SHA256 hash of the DID.
  2. The value at the DID’s position for the signal is the BTC1 Update Announcement for that DID (0 if null).
  3. For any parent node:
    1. If the values of both child nodes are 0, the value of the parent node is 0.
    2. Otherwise, the value of the parent node is the hash of the concatenation of the 256-bit left child value and the 256-bit right child value.

The consequence of step 1 is that the Merkle tree has up to 2256 leaves, 2256-1 nodes, and a depth of 256+1=257. This is mitigated by step 3i, which limits the tree size to only those branches where at least one leaf has a non-null data block. The presentation of the peer hashes doesn’t require direction, as the sequence of directions is determined by the DID’s position.

12.2.2 Information Leakage

To prove inclusion or non-inclusion, it is necessary to present a list of peer hashes from bottom to top. A verifier then takes the hash of the BTC1 Update (inclusion) or the hash of null (non-inclusion) and applies the algorithm above to walk up to the root. Most of the peer hashes will be zero.

The list of peer hashes must be provided by the aggregator to the DID controller. Changes in the values (from zero to non-zero or from non-zero to zero) indicate frequency of changes to other DIDs in the peer branch. Furthermore, assuming that a verifier has a DID from a past presentation with the same aggregator Beacon address:

  • a zero value in a node that encompasses the hash value of the DID is definitive proof that the DID document has not been updated; and
  • a non-zero value in a node that encompasses the hash value of the DID is statistically significant proof that the DID document has been updated.

Let’s assume that:

  • positions 0 (0000), 2 (0010), 5 (0101), 9 (1001), 13 (1101), and 14 (1110) have DIDs associated with them;
  • a signal includes updates for DIDs 2, 9, and 13; and
  • a verifier presented with DID 13 also knows, through prior presentations, about DIDs 5 and 14, but not about DIDs 0, 2, and 9.

The hash tree for the signal looks like this:

The presentation to the verifier for DID 13 includes the following:

{
  "peers": [
    "0000...0000",
    "0000...0000",
    "<< Hexadecimal of Hash 10 >>",
    "<< Hexadecimal of Hash 0 >>"
  ]
}

From this, the verifier can infer that:

  • position 12 (1100) is not allocated or has been allocated to an unknown DID that hasn’t been updated;
  • the DID at position 14 (1110) has not been updated;
  • position 15 (1111) is not allocated or has been allocated to an unknown DID that hasn’t been updated;
  • one or more unknown DIDs at positions 8-11 (1000-1011) have been updated; and
  • the DID at position 5 (0101) may have been updated (probability ≥ 1/8).

To mitigate this, inclusion and non-inclusion should be indistinguishable, i.e., there should not be a reserved value of 0 indicating a null payload. It is still necessary to identify empty branches (otherwise the hash calculation time becomes impossibly large), so the reserved value of 0 is retained for that purpose. The following (revised) algorithm meets these requirements:

  • A DID’s position is the SHA256 hash of the DID.
  • A signal- and DID-specific 256-bit nonce shall be generated by the DID controller, regardless of update or non-update status.
  • The value at the DID’s position for the signal is the nonce xored with the hash of the BTC1 Update for that signal (0 if null).
    • If the DID controller is responsible for providing the value, the nature of the signal (update or non-update) is hidden from the aggregator.
  • The value of the parent node is the hash of the concatenation of the 256-bit left child value (0 if the left branch is empty) and the 256-bit right child value (0 if the right branch is empty).
    • One or both of the left and right branches is non-empty.

Using the example above, the hash tree for the signal looks like this:

Every DID is included, so there is no longer a proof of non-inclusion. Instead, what’s being proved is the presence or absence of an update, where the absence of an update is a null document. To prove presence or absence of an update, the DID controller presents the nonce, the BTC1 Update or null, and the list of peer hashes from bottom to top.

Now, the presentation to the verifier for DID 13 includes the following:

{
  "nonce": "<< Hexadecimal of Nonce 1101 >>",
  "peers": [
    "0000...0000",
    "<< Hexadecimal of Hash 111 >>",
    "<< Hexadecimal of Hash 10 >>",
    "<< Hexadecimal of Hash 0 >>"
  ]
}

From this, the verifier can infer only that position 12 (1100) is not allocated. Having the nonce vary per signal ensures that the hash of the null value varies and so can’t be tested for across signals. Having the nonce vary per DID ensures that the verifier can’t test for non-update of other known DIDs. Peer hashes that are zero will always be zero and those that are non-zero will always be non-zero.

12.3 Optimization

The tree can be further optimized as outlined in The Libra Blockchain. The first optimization collapses empty nodes into a fixed value; this is already defined above where the hash of an empty node is zero. The second optimization is to replace subtrees containing exactly one leaf with a single node. This reduces the tree size significantly to a depth of approximately log2(n), where n is the number of leaves.

Doing this violates the requirement that the starting point be deterministic; the verifier would have to know every occupied index to infer the starting point for the DID of interest. It also requires that non-updates be included, as it would otherwise be impossible to prove non-inclusion, and the nonce is still required so that updates are indistinguishable from non-updates.

Mitigating the deterministic index issue is accomplished by setting the value to the hash of index concatenated with the the hash value provided by the DID controller. The end result is this (note that the positions of nodes Hash1001 and Hash11 are reversed due to the Mermaid layout algorithm):

Now, the presentation to the verifier for DID 13 includes the following:

{
  "nonce": "<< Hexadecimal of Nonce 1101 >>",
  "peers": [
    {
      "right": "<< Hexadecimal of Hash 1110 >>"
    },
    {
      "left": "<< Hexadecimal of Hash 1001 >>"
    },
    {
      "left": "<< Hexadecimal of Hash 0 >>"
    }
  ]
}

The only thing the verifier can infer from any presentation is the depth of the tree and therefore an estimate of the number of DIDs using the Beacon.