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
Ryan Grant | rgrant@contract.design | Digital Contract Design |
Will Abramson | will@legreq.com | Legendary Requirements |
Joe Andrieu | joe@legreq.com | Legendary Requirements |
Kevin Dean | kevin@legreq.com | Legendary Requirements |
Dan Pape | dpape@contract.design | Digital Contract Design |
Jennie Meier | jennie@contract.design | Digital Contract Design |
Contributors
Kate Sills | katelynsills@gmail.com | |
Markus Sabadello | markus@danubetech.com | |
Jintek | info@jintek.consulting |
Publication Date
20th September 2024
Licence Statement
Mozilla Public License Version 2.0
Copyright
© 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.
-
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
- 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.
- a
The specification version
and the
Bitcoin network
are encoded into a single
byte as follows:
- The first four bits (high nibble) are the
version
minus one. For this version of the specification, theversion
is1
and the high nibble is0
. - The second (remaining) four bits (low nibble) are
the Bitcoin
network
identifier, one of:0
= bitcoin (mainnet);1
= signet;2
= regtest;3
= testnet v3;4
= testnet v4;5
= mutinynet;6
-B
= reserved for future use by the specification; orC
-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:
- Set
version
to1
. - Start with the first nibble (the higher nibble of the first byte).
- Add the value of the current nibble to
version
. - If the value of the nibble is hexadecimal
F
(decimal15
), 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, numbernetwork
- 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”
- a compressed secp256k1 public key if
Encode the did:btc1 identifier as follows:
- If
idType
is not a valid value per above, raiseinvalidDid
error. - If
version
is greater than1
, raiseinvalidDid
error. - If
network
is not a valid value per above, raiseinvalidDid
error. - if
network
is a number and is outside the range of 1-4, raiseinvalidDid
error. - If
idType
is “key” andgenesisBytes
is not a valid compressed secp256k1 public key, raiseinvalidDid
error. - Map
idType
tohrp
from the following:- “key” - “k”
- “external” - “x”
- Create an empty
nibbles
numeric array. - Set
fCount
equal to(version - 1) / 15
, rounded down. - Append hexadecimal
F
(decimal15
) tonibbles
fCount
times. - Append
(version - 1) mod 15
tonibbles
. - If
network
is a string, append the numeric value from the following map tonibbles
:- “bitcoin” -
0
- “signet” -
1
- “regtest” -
2
- “testnet3” -
3
- “testnet4” -
4
- “mutinynet” -
5
- “bitcoin” -
- If
network
is a number, appendnetwork + 11
tonibbles
. - If the number of entries in
nibbles
is odd, append0
. - Create a
dataBytes
byte array fromnibbles
, whereindex
is from0
tonibbles.length / 2 - 1
andencodingBytes[index] = (nibbles[2 * index] << 4) | nibbles[2 * index + 1]
. - Append
genesisBytes
todataBytes
. - Set
identifier
to “did:btc1:”. - Pass
hrp
anddataBytes
to the Bech32m Encoding algorithm, retrievingencodedString
. - Append
encodedString
toidentifier
. - Return
identifier
.
4.3 did:btc1 Identifier Decoding
Given:
identifier
- required, a string did:btc1 identifier
Decode the did:btc1 identifier as follows:
- Split
identifier
into an array ofcomponents
at the colon:
character. - If the length of the
components
array is not3
, raiseinvalidDid
error. - If
components[0]
is not “did”, raiseinvalidDid
error. - If
components[1]
is not “btc1”, raisemethodNotSupported
error. - Set
encodedString
tocomponents[2]
. - Pass
encodedString
to the Bech32m Decoding algorithm, retrievinghrp
anddataBytes
. - If the Bech32m
Decoding algorithm fails, raise
invalidDid
error. - Map
hrp
toidType
from the following:- “k” - “key”
- “x” - “external”
- other - raise
invalidDid
error
- Set
version
to1
. - If at any point in the remaining steps there are not
enough nibbles to complete the process, raise
invalidDid
error. - Start with the first nibble (the higher nibble of
the first byte) of
dataBytes
. - Add the value of the current nibble to
version
. - If the value of the nibble is hexadecimal
F
(decimal15
), 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. - If
version
is greater than1
, raiseinvalidDid
error. - Advance to the next nibble and set
networkValue
to its value. - Map
networkValue
tonetwork
from the following:0
- “bitcoin”1
- “signet”2
- “regtest”3
- “testnet3”4
- “testnet4”5
- “mutinynet”6
-B
- raiseinvalidDid
errorC
-F
-networkValue - 11
- If the number of nibbles consumed is odd:
- Advance to the next nibble and set
fillerNibble
to its value. - If
fillerNibble
is not0
, raiseinvalidDid
error.
- Advance to the next nibble and set
- Set
genesisBytes
to the remainingdataBytes
. - If
idType
is “key” andgenesisBytes
is not a valid compressed secp256k1 public key, raiseinvalidDid
error. - Return
idType
,version
,network
, andgenesisBytes
.
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:
- How a BTC1 Beacon can be established and added as a service to a DID document.
- How BTC1 Update Announcements are broadcast within Beacon Signals.
- 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:
- a BTC1 Update Announcement;
- the hash of a Beacon Announcement Map; or
- the 32 bytes of an optimized sparse Merkle tree root, where each leaf node is deterministically selected by a did:btc1 identifier and contains a hash associated with the did:btc1 identifier.
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:
btc1Update
- the BTC1 Update whose SHA256 hash is the BTC1 Update Announcement to be broadcast in the Beacon Signalbtc1UpdateAnnouncement
- the BTC1 Update Announcement to be broadcast in the Beacon Signal
cas
- optional, one of:- “ipfs”
Construct a Bitcoin transaction that spends from the Beacon address on the selected network:
- If
network
is not a valid value per above, raise InvalidParameter error. - if
network
is a number and is outside the range of 1-4, raise InvalidParameter error. - If
cas
is defined and is not a valid value per above, raise InvalidParameter error. - Set
bitcoinAddress
to the decoding ofserviceEndpoint
following BIP21. - Ensure
bitcoinAddress
is funded; if not, fund this address. - If
btc1UpdateAnnouncement
is not defined, setbtc1UpdateAnnouncement
to the result of passingbtc1Update
to the JSON Canonicalization and Hash algorithm. - Initialize
spendTx
to a Bitcoin transaction that spends a transaction controlled by thebitcoinAddress
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. - Retrieve the cryptographic material, e.g., private
key or signing capability, associated with the
bitcoinAddress
. How this is done is left to the implementer. - Sign the
spendTx
. - Broadcast
spendTx
on the Bitcoin network defined bynetwork
. - If
cas
andbtc1Update
are defined, publishbtc1Update
to the CAS network defined bycas
.
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:
- Beacon Participants submit their updates to the Beacon Aggregator.
- When the signal conditions are met, the Beacon Aggregator initiates the construction of the Beacon Signal and passes to the Beacon Participants for verification and signing.
- Once all Beacon Participants have approved and partially signed the transaction, the final signed transaction is constructed and broadcast on the Bitcoin network.
- If a CAS is defined for the Beacon, the Beacon Aggregator publishes all files to the CAS.
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):
btc1Update
- a BTC1 Updatebtc1UpdateAnnouncement
- the SHA256 hash in binary form of a BTC1 Update
Create a Beacon Announcement Map as follows:
- If
unnormalizedBeaconAnnouncementMap
contains a duplicatedid
, raise InvalidParameter error. - Create empty
beaconAnnouncementMap
. - For each
did
inunnormalizedBeaconAnnouncementMap
:- If the value is a BTC1
Update:
- Set
hashBytes
to the result of passingbtc1Update
to the JSON Canonicalization and Hash algorithm. - Set
hashString
to the hexadecimal string representation ofhashBytes
.
- Set
- If the value is a SHA256 hash in binary form:
- Set
hashString
to the hexadecimal string representation ofbtc1UpdateAnnouncement
.
- Set
- Add
did
(key) andhashString
(value) tobeaconAnnouncementMap
.
- If the value is a BTC1
Update:
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 URIbeaconAnnouncementMap
- required, Beacon Announcement Map created as above
Construct a Bitcoin transaction that spends from the Beacon address on the selected network:
- If
network
is not a valid value per above, raise InvalidParameter error. - if
network
is a number and is outside the range of 1-4, raise InvalidParameter error. - Set
bitcoinAddress
to the decoding ofserviceEndpoint
following BIP21. - Ensure
bitcoinAddress
is funded; if not, fund this address. - Set
hashBytes
to the result of passing the JSON representation ofbeaconAnnouncementMap
to the JSON Canonicalization and Hash algorithm. - Initialize
unsignedSpendTx
to a Bitcoin transaction that spends a transaction controlled by thebitcoinAddress
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 aboveunsignedSpendTx
- required, unsigned Beacon signal constructed as above
Validate the Beacon Announcement Map and the unsigned Beacon signal:
- 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. - Set
hashBytes
to the result of passing the JSON representation ofbeaconAnnouncementMap
to the JSON Canonicalization and Hash algorithm. - Validate that
unsignedSpendTx
is spending from the correct Bitcoin address. - 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 aboveunnormalizedBeaconAnnouncementMap
- required, as abovepsbts
- required, partially signed Bitcoin transactionscas
- optional, one of:- “ipfs”
Spend the transaction and publish to CAS:
- If
cas
is defined and is not a valid value per above, raise InvalidParameter error. - Set
spendTx
to the aggregation of the partially signed Bitcoin transactionspsbts
into a single transaction. - Broadcast
spendTx
on the Bitcoin network. - If
cas
is defined:
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:
- Beacon Participants submit their updates to the Beacon Aggregator.
- When the signal conditions are met, the Beacon Aggregator requests missing entries from Beacon Participants, initiates the construction of the Beacon Signal, and passes data to the Beacon Participants for verification and signing.
- Once all Beacon Participants have approved and partially signed the transaction, the final signed transaction is constructed and broadcast on the Bitcoin network.
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 URIhashBytes
- required, root hash of optimized SMT
Construct a Bitcoin transaction that spends from the Beacon address on the selected network:
- If
network
is not a valid value per above, raise InvalidParameter error. - if
network
is a number and is outside the range of 1-4, raise InvalidParameter error. - Set
bitcoinAddress
to the decoding ofserviceEndpoint
following BIP21. - Ensure
bitcoinAddress
is funded; if not, fund this address. - Initialize
unsignedSpendTx
to a Bitcoin transaction that spends a transaction controlled by thebitcoinAddress
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 SMTindex
- required, index provided by DID controller
Calculate the path to the root for the index:
- Set
path
to empty array. - Set
node
to leaf node forindex
. - While
node
is not root ofsmt
:- Set
parentNode
to parent ofnode
. - If
node
is left ofparentNode
, add{right: <rightHashString>}
topath
, whererightHashString
is the hexadecimal string representation of the value atparentNode.rightNode
. - If
node
is right ofparentNode
, add{left: <leftHashString>}
topath
, whereleftHashString
is the hexadecimal string representation of the value atparentNode.leftNode
.
- Set
- Return
path
.
6.6.2.3 Validate Proof Paths Map and Unsigned Beacon Signal
Given:
pathsMap
- required, proof paths map constructed as aboveunsignedSpendTx
- required, unsigned Beacon signal constructed as above
Validate the proof paths map and the unsigned Beacon signal:
- Validate that
unsignedSpendTx
is spending from the correct Bitcoin address. - For each
did
expected to be in the Beacon Signal:- Set
index
tohash(did)
. - Set
path
to the value atindex
and remove it from the map. - If
path
is undefined, raise InvalidParameter error. - Extract the current
nonce
andbtc1Update
fordid
from local storage. - If
btc1Update
is defined, setbtc1UpdateAnnouncement
to the result of passingbtc1Update
to the JSON Canonicalization and Hash algorithm and sethashBytes
tohash(index + hash(nonce ^ btc1UpdateAnnouncement))
, otherwise sethashBytes
tohash(index + hash(nonce))
. - For each
step
inpath
:- Validate that
step
has a single key-value pair. - Extract
key
andvalue
fromstep
. - If
key
is"left"
, sethashBytes
tohash(value + hashBytes)
; otherwise, ifkey
is"right"
, sethashBytes
tohash(hashBytes + value)
; otherwise, raise InvalidParameter error.
- Validate that
- Validate that the last transaction output of
unsignedSpendTx
is[OP_RETURN, OP_PUSHBYTES32, <hashBytes>]
. - If
btc1UpdateAnnouncement
is defined, construct assmtProof
the object{id: <hashString>, nonce: <nonce>, updateId: <btc1UpdateHashString>, path: <path>}
, otherwise construct assmtProof
the object{id: <hashString>, nonce: <nonce>, path: <path>}
, wherehashString
is the hexadecimal string representation ofhashBytes
andbtc1UpdateHashString
is the hexadecimal string representation ofbtc1UpdateAnnouncement
. - Store
smtProof
for later presentation to verifiers.
- Set
- If
pathsMap
is not empty, raise InvalidParameter error.
6.6.2.4 Finalize
Given:
psbts
- required, partially signed Bitcoin transactions
Spend the transaction:
- Set
spendTx
to the aggregation of the partially signed Bitcoin transactionspsbts
into a single transaction. - Broadcast
spendTx
on the Bitcoin network.
6.7 Processing Signals
Given:
did
- required, the did:btc1 identifier whose signals are to be processedsidecarDocuments
- required, array of documents required for resolution not stored on a CAS,including:- Initial DID document, if
did
was constructed withidType
of “external” (did
has the form “did:btc1:x1…”) - Map documents for BTC1
Beacons with services of
beaconType
“MapBeacon”. - BTC1 Updates for each BTC1 Update Announcement
- Initial DID document, if
smtProofs
- required for services ofbeaconType
“SMTBeacon”, array of SMT proofs of inclusion or non-inclusioncas
- optional, one of:- “ipfs”
targetVersionId
- optional, DID document version ID requiredtargetVersionTime
- optional, DID document version time required
Process the Beacon Signals to reconstruct the DID document:
- If
cas
is defined and is not a valid value per above, raise InvalidParameter error. - Set
sidecarDocumentsMap
to empty map. - For each
sidecarDocument
insidecarDocuments
:- Set
hashBytes
to the result of passingsidecarDocument
to the JSON Canonicalization and Hash algorithm. - Set
id
to the hexadecimal string representation ofhashBytes
. - Add
id
andsidecarDocument
tosidecarDocumentsMap
.
- Set
- Set
smtProofsMap
to empty map. - For each
smtProof
insmtProofs
:- Add
smtProof.id
andsmtProof
tosmtProofsMap
.
- Add
- If
did
was constructed withidType
“key”, setdidDocument
to the deterministically generated initial document. - If
did
was constructed withidType
“external”:- Extract
genesisBytes
fromdid
. - Set
id
to the hexadecimal string representation ofgenesisBytes
. - Get
didDocument
fromsidecarDocumentsMap
by itsid
if available, or from CAS by itsid
if not andcas
is defined. - Update placeholder values in
didDocument
withdid
as required. - If
didDocument
is not a valid DID document, raise InvalidDidUpdate error.
- Extract
- Until terminated:
- Set
btc1Update
to null. - For each
service
indidDocument.service
whereservice.type
is “BTC1Beacon”:- Get the next transaction from the Bitcoin address at
service.serviceEndpoint
. - If
targetVersionTime
is defined and is less than the transaction time, skip to nextservice
. - If the last output transaction is not of the form
[OP_RETURN, OP_PUSHBYTES32, <hashBytes>]
, skip to nextservice
. - Extract
hashBytes
from the last output transaction. - If
service.beaconType
is not “SingletonBeacon”, “MapBeacon”, or “SMTBeacon”, raise InvalidParameter error. - If
service.beaconType
is “SingletonBeacon”, settempBtc1Update
to the result of Process Singleton Beacon Signal. - If
service.beaconType
is “MapBeacon”, settempBtc1Update
to the result of Process Map Beacon Signal. - If
service.beaconType
is “SMTBeacon”, settempBtc1Update
to the result of Process SMT Beacon Signal. - If
tempBtc1Update
is null, skip to nextservice
. - Set
tempDidDocument
to transformation ofdidDocument
withtempBtc1Update
. - If
tempDidDocument.versionId
≠didDocument.versionId + 1
, skip to nextservice
. - If
btc1Update
is not null andtempBtc1Update
≠btc1Update
, raise InvalidDidUpdate error. - Set
btc1Update
totempBtc1Update
.
- Get the next transaction from the Bitcoin address at
- If
btc1Update
is null, terminate. - Set
didDocument
to transformation ofdidDocument
withbtc1Update
. - If
didDocument
is not a valid DID document, raise InvalidDidUpdate error. - If
didDocument.id
is not the same as the did:btc1 identifier, raise InvalidDidUpdate error. - If
didDocument
has no BTC1 Beacon service types (i.e., no services whereservice.type
is “BTC1Beacon”), raise InvalidDidUpdate error. - If
targetVersionId
is defined anddidDocument.versionId
=targetVersionId
, terminate.
- Set
- If
targetVersionId
is defined anddidDocument.versionId
≠targetVersionId
, raise InvalidDidUpdate error. - If
targetVersionId
is not defined: 1. For eachservice
indidDocument.service
whereservice.type
is “BTC1Beacon”:- Get the next transaction from the Bitcoin address at
service.serviceEndpoint
. - If
targetVersionTime
is defined and is less than the transaction time, skip to nextservice
. - If the last output transaction is of the form
[OP_RETURN, OP_PUSHBYTES32, <hashBytes>]
, raise InvalidDidUpdate error.
- Get the next transaction from the Bitcoin address at
- Return
didDocument
.
6.7.1 Process Singleton Beacon Signal
- Set
id
to the hexadecimal string representation ofhashBytes
. - Get
btc1Update
fromsidecarDocumentsMap
by itsid
if available, or from CAS by itsid
if not andcas
is defined. - If
btc1Update
is undefined, raise InvalidDidUpdate error. - Set
btc1Update
- Return
btc1Update
.
NOTE: The act of retrieving from
sidecarDocumentsMap
or CAS validates the document hash.
6.7.2 Process Map Beacon Signal
- Set
id
to the hexadecimal string representation ofhashBytes
. - Get
map
fromsidecarDocumentsMap
by itsid
if available, or from CAS by itsid
if not andcas
is defined. - If
map
is undefined, raise InvalidDidUpdate error. - Set
updateId
to the value ofmap.<did>
. - If
updateId
is undefined, return null. - Get
btc1Update
fromsidecarDocumentsMap
by itsupdateId
if available, or from CAS by itsupdateId
if not andcas
is defined. - If
btc1Update
is undefined, raise InvalidDidUpdate error. - Return
btc1Update
.
NOTE: The act of retrieving from
sidecarDocumentsMap
or CAS validates the document hash.
6.7.3 Process SMT Beacon Signal
- Set
id
to the hexadecimal string representation ofhashBytes
. - Get
smtProof
fromsmtProofsMap
by itsid
. - If
smtProof
is undefined, raise InvalidDidUpdate error. - Set
index
tohash(did)
. - Set
nonce
to the value ofsmtProof.nonce
. - Set
updateId
to the value ofsmtProof.updateId
. - If
updateId
is defined, setbtc1UpdateAnnouncement
to the binary representation ofupdateId
and setverifyHashBytes
tohash(index + hash(nonce ^ btc1UpdateAnnouncement))
, otherwise setverifyHashBytes
tohash(index + hash(nonce))
. - For each
step
insmtProof.path
:- Validate that
step
has a single key-value pair. - Extract
key
andvalue
fromstep
. - If
key
is"left"
, setverifyHashBytes
tohash(value + verifyHashBytes)
; otherwise, ifkey
is"right"
, setverifyHashBytes
tohash(verifyHashBytes + value)
; otherwise, raise InvalidDidUpdate error.
- Validate that
- If
verifyHashBytes
≠hashBytes
, raise InvalidDidUpdate error. - If
updateId
is undefined, return null. - Get
btc1Update
fromsidecarDocumentsMap
by itsupdateId
if available, or from CAS by itsupdateId
if not andcas
is defined. - If
btc1Update
is undefined, raise InvalidDidUpdate error. - 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; bytesversion
- 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; stringinitialDocument
- the valid first version of a DID document for a given did:btc1 identifier.
The steps are as follows:
- Set
idType
to “key”. - Set
version
to1
. - Set
network
to the desired network. - Set
genesisBytes
topubKeyBytes
. - Pass
idType
,version
,network
, andgenesisBytes
to the did:btc1 Identifier Encoding algorithm, retrievingid
. - Set
did
toid
. - Set
initialDocument
to the result of passingdid
into the Read algorithm. - Return
did
andinitialDocument
.
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 theidentifier
replaced with the placeholder value throughout all fields (e.g. the id field)did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
. It SHOULD include at least oneverificationMethod
andservice
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; stringinitialDocument
- the valid first version of a DID document for a given btc1 identifier.
The steps are as follows:
- Set
idType
to “external”. - Set
version
to1
. - Set
network
to the desired network. - Set
genesisBytes
to the result of passingintermediateDocument
into the JSON Canonicalization and Hash algorithm. - Pass
idType
,version
,network
, andgenesisBytes
to the did:btc1 Identifier Encoding algorithm, retrievingid
. - Set
did
toid
. - Set
initialDocument
to a copy of theintermediateDocument
. - Replace all
did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
values in theinitialDocument
with thedid
. - 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. - Return
did
andinitialDocument
.
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; stringresolutionOptions
- an object that extends the default options per the DID Resolution specification; the below list is not intended to be exhaustive; OPTIONAL; objectversionId
- the version of the DID and DID document, an incrementing integer starting from 1; OPTIONAL; integerversionTime
- a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integersidecarData
- 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:
- Pass
identifier
to the did:btc1 Identifier Decoding algorithm, retrievingidType
,version
,network
, andgenesisBytes
. - Set
identifierComponents
to a map ofidType
,version
,network
, andgenesisBytes
. - Set
initialDocument
to the result of running the algorithm in Resolve Initial DID Document passing in theidentifier
,identifierComponents
andresolutionOptions
. - Set
targetDocument
to the result of running the algorithm in Resolve Target Document passing ininitialDocument
andresolutionOptions
. - 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; stringidentifierComponents
- The decoded parts of a did:btc1 identifier; REQUIRED; objectidType
- the type of identifier (KEY
orEXTERNAL
); REQUIRED; stringversion
- the identifier version; REQUIRED; integernetwork
- the Bitcoin network used for the identifier; REQUIRED; stringgenesisBytes
- the originating public key; REQUIRED; bytes
resolutionOptions
- options that extends the default options per the DID Resolution specification; OPTIONAL; objectversionId
- the version of the DID and DID document, an incrementing integer starting from 1; the below list is not intended to be exhaustive; OPTIONAL; integerversionTime
- a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integersidecarData
- data necessary for resolving a DID such as BTC1 Updates and SMT proofs.; OPTIONAL; objectnetwork
- 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:
- If
identifierComponents.idType
value is “key”, then set theinitialDocument
to the result of running the algorithm in Deterministically Generate Initial DID document passing in theidentifier
andidentifierComponents
values. - Else If
identifierComponents.idType
value is “external”, then set theinitialDocument
to the result of running External Resolution passing in theidentifier
,identifierComponents
andresolutionOptions
values. - Else MUST raise
invalidHRPValue
error. - 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; stringidentifierComponents
- The decoded parts of a did:btc1 identifier; REQUIRED; objectidType
- the type of identifier (KEY
orEXTERNAL
); REQUIRED; stringversion
- the identifier version; REQUIRED; integernetwork
- the Bitcoin network used for the identifier; REQUIRED; stringgenesisBytes
- 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:
- Set
keyBytes
toidentifierComponents.genesisBytes
. - Initialize an
initialDocument
variable as an empty object. - Set
initialDocument.id
to theidentifier
. - Initialize a
contextArray
to empty array:- Append the DID Core v1.1 context “https://www.w3.org/ns/did/v1.1”.
- Append the did:btc1 context “https://btc1.dev/context/v1”.
- Set
initialDocument['@context]'
tocontextArray
.
- Initialize a
controllerArray
to empty array:- Append the
identifier
. - Set
initialDocument.controller
tocontrollerArray
.
- Append the
- Create an initial verification method:
- Initialize
verificationMethod
to an empty object. - Set
verificationMethod.id
to{identifier}#initialKey
. - Set
verificationMethod.type
to “Multikey”. - Set
verificationMethod.controller
toidentifier
. - Set
verificationMethod.publicKeyMultibase
to the result of the encoding algorithm in BIP340 Multikey.
- Initialize
- Set
initialDocument.verificationMethod
to an array containingverificationMethod
. - Initialize a
tempArray
variable to an array with the single elementverificationMethod.id
. - Set the
authentication
,assertionMethod
,capabilityInvocation
, and thecapabilityDelegation
properties ininitialDocument
to a copy of thetempArray
variable. - Set the
initialDocument.services
property ininitialDocument
to the result of passing theidentifier
,keyBytes
andidentifierComponents.network
to the Deterministically Generate Beacon Services algorithm. - 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; stringkeyBytes
- a compressed SEC encoded secp256k1 public key; REQUIRED; bytesnetwork
- 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 propertiestype
- 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; stringserviceEndpoint
- 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:
- Initialize a
services
variable to an empty array. - Set
serviceId
to{identifier}#initialP2PKH
. - Set
beaconAddress
to the result of generating a Pay-to-Public-Key-Hash Bitcoin address from thekeyBytes
for the appropriatenetwork
. - Set
p2pkhBeacon
to the result of passingserviceId
, andbeaconAddress
to [Establish Singleton Beacon]. - Push
p2pkhBeacon
toservices
. - Set
serviceId
to{identifier}#initialP2WPKH
. - Set
beaconAddress
to the result of generating a Pay-to-Witness-Public-Key-Hash Bitcoin address from thekeyBytes
for the appropriatenetwork
. - Set
p2wpkhBeacon
to the result of passingserviceId
, andbeaconAddress
to [Establish Singleton Beacon]. - Push
p2wpkhBeacon
toservices
. - Set
serviceId
to{identifier}#initialP2TR
. - Set
beaconAddress
to the result of generating a Pay-to-Taproot Bitcoin address from thekeyBytes
for the appropriatenetwork
. - Set
p2trBeacon
to the result of passingserviceId
, andbeaconAddress
to [Establish Singleton Beacon]. - Push
p2trBeacon
toservices
. - 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; stringidentifierComponents
- The decoded parts of a did:btc1 identifier; REQUIRED; objectidType
- the type of identifier (KEY
orEXTERNAL
); REQUIRED; stringversion
- the identifier version; REQUIRED; integernetwork
- the Bitcoin network used for the identifier; REQUIRED; stringgenesisBytes
- 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; objectversionId
- the version of the DID and DID document, an incrementing integer starting from 1; OPTIONAL; integerversionTime
- a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integersidecarData
- data necessary for resolving a DID such as BTC1 Updates and SMT proofs.; OPTIONAL; objectnetwork
- the Bitcoin network used for resolution; OPTIONAL; string; default="bitcoin"
It returns the following output:
initialDocument
- a valid initial DID document: for the given identifier; object
The steps are as follows:
- If
resolutionOptions.sidecarData.initialDocument
is not null, setinitialDocument
to the result of passingidentifier
,identifierComponents
, andresolutionOptions.sidecarData.initialDocument
into algorithm Sidecar Initial DID Document Validation. - Else set
initialDocument
to the result of passingidentifier
andidentifierComponents
to the CAS Retrieval algorithm. - Validate
initialDocument
is a conformant DID document according to the DID Core 1.1 specification. Else MUST raiseinvalidDidDocument
error. - 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; stringidentifierComponents
- The decoded parts of a did:btc1 identifier; REQUIRED; objectidType
- the type of identifier (KEY
orEXTERNAL
); REQUIRED; stringversion
- the identifier version; REQUIRED; integernetwork
- the Bitcoin network used for the identifier; REQUIRED; stringgenesisBytes
- 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:
initialDocument
- a valid initial DID document for the given identifier.
The steps are as follows:
- Set
intermediateDocumentRepresentation
to a copy of theinitialDocument
. - Find and replace all values of
identifier
contained within theintermediateDocumentRepresentation
with the string (did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
). - Set
hashBytes
to the SHA256 hash of theintermediateDocumentRepresentation
. - If
hashBytes
does not equalidentifierComponents.genesisBytes
MUST throw aninvalidDid
error. - 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; stringidentifierComponents
- The decoded parts of a did:btc1 identifier; REQUIRED; objectidType
- the type of identifier (KEY
orEXTERNAL
); REQUIRED; stringversion
- the identifier version; REQUIRED; integernetwork
- the Bitcoin network used for the identifier; REQUIRED; stringgenesisBytes
- the originating intermediate DID document; REQUIRED; bytes
It returns the following output:
initialDocument
- a valid initial DID document for the given identifier.
The steps are as follows:
- Set
hashBytes
toidentifierComponents.genesisBytes
. - Set
cid
to the result of convertinghashBytes
to a IPFS v1 CID. - Set
intermediateDocumentRepresentation
to the result of fetching thecid
against a Content Addressable Storage (CAS) system such as IPFS. - Set
initialDocument
to the copy of theintermediateDocumentRepresentation
. - Replace the string
(
did:btc1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
) with theidentifier
throughout theinitialDocument
. - 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; objectresolutionOptions
- options that extends the default options per the DID Resolution specification; the below list is not intended to be exhaustive; OPTIONAL; objectversionId
- the version of the DID and DID document, an incrementing integer starting from 1; OPTIONAL; integerversionTime
- a timestamp used during resolution as a bound for when to stop resolving; OPTIONAL; integersidecarData
- data necessary for resolving a DID such as BTC1 Updates and SMT proofs.; OPTIONAL; objectnetwork
- 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:
- If
resolutionOptions.versionId
is not null, settargetVersionId
toresolutionOptions.versionId
. - Else if
resolutionOptions.versionTime
is not null, settargetTime
toresolutionOptions.versionTime
. - Else set
targetTime
to the UNIX timestamp for now at the moment of execution. - Set
signalsMetadata
toresolutionOptions.sidecarData.signalsMetadata
. - Set
currentVersionId
to 1. - If
currentVersionId
equalstargetVersionId
returninitialDocument
. - Set
btc1UpdateHashHistory
to an empty array. - Set
didDocumentHistory
to an array containing theinitialDocument
. - Set
contemporaryBlockheight
to 0. - Set
contemporaryDIDDocument
to theinitialDocument
. - Set
targetDocument
to the result of calling the Traverse Bitcoin Blockchain History algorithm passing incontemporaryDIDDocument
,contemporaryBlockheight
,currentVersionId
,targetVersionId
,targetTime
,didDocumentHistory
,btc1UpdateHashHistory
,signalsMetadata
, andnetwork
. - 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 thecontemporaryBlockheight
. 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 thetargetTime
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 thecontemporaryDIDDocument
; 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 theresolutionOptions
; 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; objectproofs
- a Sparse Merkle Tree proof that the providedbtc1Update
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 thenetwork
; 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 tocontemporaryBlockheight
.
The steps are as follows:
- Set
contemporaryHash
to the result of passingcontemporaryDIDDocument
into the JSON Canonicalization and Hash algorithm. - Find all BTC1
Beacons in
contemporaryDIDDocument.service
whereservice.type
equals one ofSingletonBeacon
,CIDAggregateBeacon
andSMTAggregateBeacon
. - For each
beacon
inbeacons
convert thebeacon.serviceEndpoint
to a Bitcoin address following BIP21. Setbeacon.address
to the Bitcoin address. - Set
nextSignals
to the result of calling algorithm Find Next Signals passing incontemporaryBlockheight
,beacons
andnetwork
. - If
nextSignals
is empty, returncontemporaryDIDDocument
. - If
nextSignals[0].blocktime
is greater thantargetTime
, returncontemporaryDIDDocument
. - Set
contemporaryBlockheight
tonextSignals[0].blockheight
. - Set
updates
to the result of calling algorithm Process Beacon Signals passing innextSignals
andsignalsMetadata
. - Set
orderedUpdates
to the list ofupdates
ordered by thetargetVersionId
property. - For
update
inorderedUpdates
:- If
update.targetVersionId
is less than or equal tocurrentVersionId
, pushcontemporaryHash
into thebtc1UpdateHashHistory
list and run the Confirm Duplicate Update algorithm passing inbtc1Update
andbtc1UpdateHashHistory
. - If
update.targetVersionId
equalscurrentVersionId + 1
:- Check that the base58 decoding of
update.sourceHash
equalscontemporaryHash
, else MUST raiselatePublishing
error. - Set
contemporaryDIDDocument
to the result of calling Apply DID Update algorithm passing incontemporaryDIDDocument
,update
. - Push
contemporaryDIDDocument
ontodidDocumentHistory
. - Increment
currentVersionId
. - Set
unsecuredUpdate
to a copy of theupdate
object. - Remove the
proof
property from theunsecuredUpdate
object. - Set
updateHash
to the result of passingunsecuredUpdate
into the JSON Canonicalization and Hash algorithm. - Push
updateHash
ontobtc1UpdateHashHistory
. - Set
contemporaryHash
to result of passingcontemporaryDIDDocument
into the JSON Canonicalization and Hash algorithm.
- Check that the base58 decoding of
- If
update.targetVersionId
is greater thancurrentVersionId + 1
, MUST throw a LatePublishing error.
- If
- Increment
contemporaryBlockheight
. - Set
targetDocument
to the result of calling the Traverse Bitcoin Blockchain History algorithm passing incontemporaryDIDDocument
,contemporaryBlockheight
,currentVersionId
,targetVersionId
,targetTime
,didDocumentHistory
,btc1UpdateHashHistory
,signalsMetadata
, andnetwork
. - If
targetVersionId
in not null, settargetDocument
to the index at thetargetVersionId
of thedidDocumentHistory
array. - 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 eitherSingletonBeacon
,CIDAggregateBeacon
, orSMTAggregateBeacon
.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 thenetwork
; REQUIRED; string.
It returns the following output:
nextSignals
- an array ofsignal
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:
- Set
signals
to an empty array. - For each
beacon
inbeacons
:- Set
beaconSpends
to the set of all Bitcoin transactions on the specifiednetwork
that spend at least one transaction input controlled by thebeacon.address
with a blockheight greater than or equal to thecontemporaryBlockheight
. - Filter the
beaconSpends
, identifying all transactions whose last transaction output is of the format[OP_RETURN, OP_PUSHBYTES32, <32bytes>]
. - For each of the filtered
beaconSpends
, push the followingbeaconSignal
object onto thesignals
array.
{ "beaconId": "${beaconService.id}", "beaconType": "${beaconService.type}", "tx": "${tx}", "blockheight": "${blockheight}", "blocktime": "${blocktime}" }
- Set
- If
signals
is empty, returnsignals
. - Sort
signals
byblockheight
from lowest to highest. - Set
nextSignals
to allsignals
with the lowestblockheight
. - 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 thesignal
was announced by; REQUIRED; string.beaconType
- the type of the BTC1 Beacon that announced thesignal
; 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 theresolutionOptions
; 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 providedbtc1Update
value is the value at the leaf indexed by the did:btc1 being resolved; OPTIONAL; object.
It returns the following output:
btc1Updates
- an array of BTC1 Updates
The steps are as follows:
- Set
updates
to an empty array. - For
beaconSignal
inbeaconSignals
:- Set
type
tobeaconSignal.beaconType
. - Set
signalTx
tobeaconSignal.tx
. - Set
signalId
tosignalTx.id
. - Set
signalSidecarData
tosignalsMetadata[signalId]
. TODO: formalize structure of sidecarData - Set
btc1Update
to null. - If
type
==SingletonBeacon
:- Set
btc1Update
to the result of passingsignalTx
andsignalSidecarData
to the Process Singleton Beacon Signal algorithm.
- Set
- If
type
==CIDAggregateBeacon
:- Set
btc1Update
to the result of passingsignalTx
andsignalSidecarData
to the [Process CIDAggregate Beacon Signal] algorithm.
- Set
- If
type
==SMTAggregateBeacon
:- Set
btc1Update
to the result of passingsignalTx
andsignalSidecarData
to the [Process SMTAggregate Beacon Signal] algorithm.
- Set
- If
btc1Update
is not null, pushbtc1Update
toupdates
.
- Set
- 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:
- Let
unsecuredUpdate
be a copy of theupdate
object. - Remove the
proof
property from theunsecuredUpdate
object. - Let
updateHash
equal the result of passingunsecuredUpdate
into the JSON Canonicalization and Hash algorithm. - Let
updateHashIndex
equalupdate.targetVersionId - 2
. - Let
historicalUpdateHash
equalupdateHashHistory[updateHashIndex]
. - Assert
historicalUpdateHash
equalsupdateHash
, if not MUST throw a LatePublishing error. - 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 thecontemporaryBlockheight
. A DID Core conformant DID document; REQUIRED; object.update
- the BTC1 Update to apply to thecontemporaryDIDDocument
; REQUIRED; object.
It returns the following output:
targetDIDDocument
- The contemporary DID document with all updates applied to it; object.
The steps are as follows:
Set
capabilityId
toupdate.proof.capability
.Set
rootCapability
to the result of passingcapabilityId
to the Dereference Root Capability Identifier algorithm.If
rootCapability.invocationTarget
does not equalcontemporaryDIDDocument.id
androotCapability.controller
does not equalcontemporaryDIDDocument.id
, MUST throw aninvalidDidUpdate
error.Instantiate a
bip340-jcs-2025
cryptosuite
instance using the key referenced by theverificationMethod
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" }
Set
expectedProofPurpose
to “capabilityInvocation”.Set
mediaType
to “application/ld+json”.Set
documentBytes
to the bytes representation ofupdate
.Set
verificationResult
to the result of passingmediaType
,documentBytes
,cryptosuite
, andexpectedProofPurpose
into the Verify Proof algorithm defined in the Verifiable Credentials (VC) Data Integrity specification.If
verificationResult.verified
equals False, MUST raise aninvalidUpdateProof
exception.Set
targetDIDDocument
to a copy ofcontemporaryDIDDocument
.Use JSON Patch to apply the
update.patch
to thetargetDIDDOcument
.Verify that
targetDIDDocument
is conformant with the data model specified by the DID Core specification.Set
targetHash
to the result of passingtargetDIDDocument
to the JSON Canonicalization and Hash algorithm.Check that
targetHash
equals the base58 decodedupdate.targetHash
, else raise InvalidDidUpdate error.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 thesourceDocument
. The result of these transformations MUST produce a DID document conformant to the DID Core specification; REQUIRED; object.verificationMethodId
- identifier for averificationMethod
within thesourceDocument
. TheverificationMethod
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 TypesSingletonBeacon
,MapBeacon
, andSMTBeacon
; 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 theresolutionOptions
; 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, thebtc1Update
will be null; object.proofs
- A Sparse Merkle Tree proof that the providedbtc1Update
value is the value at the leaf indexed by the did:btc1 being resolved; array.
The steps are as follows:
- Set
unsecuredUpdate
to the result of passingbtc1Identifier
,sourceDocument
,sourceVersionId
, anddocumentPatch
into the Construct BTC1 Update algorithm. - Set
verificationMethod
to the result of retrieving the verificationMethod fromsourceDocument
using theverificationMethodId
. - Validate the
verificationMethod
is a BIP340 Multikey:verificationMethod.type
==Multikey
verificationMethod.publicKeyMultibase[4]
==zQ3s
- Set
unsecuredBtc1Update
to the result of passingbtc1Identifier
,unsecuredUpdate
,and
verificationMethod` to the Invoke BTC1 Update algorithm. - Set
signalsMetadata
to the result of passingbtc1Identifier
,sourceDocument
,beaconIds
andunsecuredBtc1Update
to the Announce DID Update algorithm. - Return
signalsMetadata
. It is up to implementations to ensure that thesignalsMetadata
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; stringsourceDocument
- the DID document being transformed by thedocumentPatch
; 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 thesourceDocument
. 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:
- Check that
sourceDocument.id
equalsbtc1Identifier
else MUST raiseinvalidDidUpdate
error. - Initialize
unsecuredBtc1Update
to an empty object. - 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"]
- Set
unsecuredBtc1Update.patch
todocumentPatch
. - Set
targetDocument
to the result of applying thedocumentPatch
to thesourceDocument
, following the JSON Patch specification. - Validate
targetDocument
is a conformant DID document, else MUST raiseinvalidDidUpdate
error. - Set
sourceHashBytes
to the result of passingsourceDocument
into the JSON Canonicalization and Hash algorithm. - Set
unsecuredBtc1Update.sourceHash
to the base64 ofsourceHashBytes
. - Set
targetHashBytes
to the result of passingtargetDocument
into the JSON Canonicalization and Hash algorithm. - Set
unsecuredBtc1Update.targetHash
to the base64 oftargetHashBytes
. - Set
unsecuredBtc1Update.targetVersionId
tosourceVersionId + 1
- 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:
- Set
privateKeyBytes
to the result of retrieving the private key bytes associated with theverificationMethod
value. How this is achieved is left to the implementation. - Set
rootCapability
to the result of passingbtc1Identifier
into the Derive Root Capability from did:btc1 Identifier algorithm. - Initialize
proofOptions
to an empty object. - Set
proofOptions.type
toDataIntegrityProof
. - Set
proofOptions.cryptosuite
tobip340-jcs-2025
. - Set
proofOptions.verificationMethod
toverificationMethod.id
. - Set
proofOptions.proofPurpose
tocapabilityInvocation
. - Set
proofOptions.capability
torootCapability.id
. - Set
proofOptions.capabilityAction
toWrite
. - Set
cryptosuite
to the result of executing the Cryptosuite Instantiation algorithm from the BIP340 Data Integrity specification passing inproofOptions
. - Set
btc1Update
to the result of executing the Add Proof algorithm from VC Data Integrity passingunsecuredBtc1Update
as the input document,cryptosuite
, and the set ofproofOptions
. - 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 TypesSingletonBeacon
,MapBeacon
, andSMTBeacon
; 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 theresolutionOptions
; 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, thebtc1Update
will be null; object.proofs
- A Sparse Merkle Tree proof that the providedbtc1Update
value is the value at the leaf indexed by the did:btc1 being resolved; object.
The steps are as follows:
- Set
beaconServices
to an empty array. - Set
signalMetadata
to an empty array. - For
beaconId
inbeaconIds
:- Find
beaconService
insourceDocument.service
with anid
property equal tobeaconId
. - If no
beaconService
MUST throwbeaconNotFound
error. - Push
beaconService
tobeaconServices
.
- Find
- For
beaconService
inbeaconServices
:- Set
signalMetadata
to null. - If
beaconService.type
==SingletonBeacon
:- Set
signalMetadata
to the result of passingbeaconService
andbtc1Update
to the [Broadcast Singleton Beacon Signal] algorithm.
- Set
- Else If
beaconService.type
==CIDAggregateBeacon
:- Set
signalMetadata
to the result of passingbtc1Identifier
,beaconService
andbtc1Update
to the [Broadcast CIDAggregate Beacon Signal] algorithm.
- Set
- Else If
beaconService.type
==SMTAggregateBeacon
:- Set
signalMetadata
to the result of passingbtc1Identifier
,beaconService
andbtc1Update
to the [Broadcast SMTAggregate Beacon Signal] algorithm.
- Set
- Else:
- MUST throw
invalidBeacon
error.
- MUST throw
- Set
- Merge
signalMetadata
intosignalsMetadata
. - 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 encodingdataBytes
- required, a byte array to be encoded
- Initialize
encodedString
to the output of Bech32m encoding thehrp
and thedataBytes
as described in BIP-0350. - Return
encodedString
.
11.1.2 Bech32m Decoding
Given:
encodedString
- required, the Bech32m-encoded string from a prior encoding operation
- Initialize
hrp
anddataBytes
to the result of Bech32m decoding theencodedString
as described in BIP-0350. - Return
hrp
anddataBytes
.
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
.
- Set
canonicalBytes
to the result of applying the JSON Canonicalization Scheme to thedocument
. - Set
hashBytes
to the result of applying the SHA256 cryptographic hashing algorithm to thecanonicalBytes
. - 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.
- Set
cid
to the result of convertinghashBytes
to an IPFS v1 CID. - Set
content
to the result of fetching thecid
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? - If content for
cid
cannot be found, setcontent
to null. - 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.
- Define
rootCapability
as an empty object. - Set
rootCapability.@context
to ‘https://w3id.org/zcap/v1’. - Set
encodedIdentifier
to result of calling algorithmencodeURIComponent(identifier)
. - Set
rootCapability.id
tourn:zcap:root:${encodedIdentifier}
. - Set
rootCapability.controller
toidentifier
. - Set
rootCapability.invocationTarget
toidentifier
. - 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.
- Set
rootCapability
to an empty object. - Set
components
to the result ofcapabilityId.split(":")
. - Validate
components
:- Assert length of
components
is 4. components[0] == urn
.components[1] == zcap
.components[2] == root
.
- Assert length of
- Set
uriEncodedId
tocomponents[3]
. - Set
btc1Identifier
the result ofdecodeURIComponent(uriEncodedId)
. - Set
rootCapability.id
tocapabilityId
. - Set
rootCapability.controller
tobtc1Identifier
. - Set
rootCapability.invocationTarget
tobtc1Identifier
. - 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:
- A DID’s position is the SHA256 hash of the DID.
- The value at the DID’s position for the signal is the BTC1 Update Announcement for that DID (0 if null).
- For any parent node:
- If the values of both child nodes are 0, the value of the parent node is 0.
- 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.