Unchained's wallet infrastructure faces a unique engineering challenge: The majority of bitcoin standards, mental models, and utilities are optimized not for collaborative custody, but for singlesignature or non-collaborative multisignature custody.
This is the case even when considering large custodial exchanges, where the underlying assets of thousands of unique users may be handled by that single entity. In either case, the technical management of a singlesig or non-collaborative multisig “wallet” (a term we will more concretely define later on) is relatively straightforward even if the system it functions behind and the security it requires remains complex.
Consider a user of an exchange who wants to deposit bitcoin to a new address. On the surface this is a simple operation—as far as the user is concerned, there is no relationship between that address and the one he or she was given before. Although the exchange (hopefully) keeps track of this information in order to track an individual’s balance, there’s nothing inherent to those addresses that connects them. The exchange itself simply associates each address with the user to which it was assigned.
In fact, this is similar to how wallets in early versions of bitcoin worked. Your machine would generate a new random private-public key pair for every new payment destination you wanted. A wallet.dat file would keep track of each of these, and a database would keep track of balances for every address (hashed public key) it knew about. If you lost access to any piece of this data, you lost access to the money it represented ownership over.
Hierarchical Deterministic ("HD") wallets as part of BIP32 fixed this by giving us a system by which a single secret or “seed phrase” could be used to deterministically generate a practically infinite number of new private-public key pairs. As long as you had this seed phrase, you could recover the data necessary to claim ownership over the funds. This is great for scaling self-custody wallet backups since there’s less data required for a recovery. For larger-scale operations like exchanges, however, HD wallets risk central control over massive funds. Instead, these organizations likely track funds across large proprietary databases, and abstract this complexity away from users in exchange for trust.
Collaborative custody exists in a hybrid of these two worlds. A platform like Unchained enables the growth of a network of connections between not just individual users but also actual collaborative owners (i.e. the keys) built on top of standards, such as HD wallets, that enable safe and scalable wallet management for self-custody.
While this presents a large number of interesting technical challenges, in this post I will be focusing on the mental model, or the “domain model”, we have developed and implemented over the past few years at Unchained. We have used this model to construct a reliable and robust infrastructure in this still relatively new paradigm.
But first, why does the language we use to talk about our systems matter?
The term “domain-driven design” was coined by Eric Evans in 2003 in his book whose title gives a hint as to the answer: Domain-Driven Design: Tackling Complexity in the Heart of Software. The idea is that, in programming, our ultimate goal is to build tools that both represent and interact with elements of the real world. Software can adapt to these realities with near infinite flexibility, but this seeming superpower can often lead to labyrinthine software: hard to understand and even harder to maintain.
“There are only two hard things in Computer Science: cache invalidation, naming things, and off-by-1 errors" —Anonymous (unknown)
The thesis put forth by Evans is that the software we write should reflect the real world systems (i.e. the “business domain”) we are modeling down to the same terminology used by the domain’s experts. This allows for complex ideas to be modeled in intuitive ways, with patterns and relationships emerging almost organically as they have in their real world systems. This can result in cleaner, easier to maintain, and less error-prone code.
One way that this problem manifested itself at Unchained was one of those classic startup problems: we were a little too early. Consider that when Unchained was founded at the end of 2016 there were no PSBTs, no descriptors, and no miniscript. Electrum was the only real way to build a multisig wallet, and even BIP 39 seed phrases were only a few years old.
Our first and only product at the time was our loans, and while the wallet was mostly standards compliant, the keys were all managed by Unchained (i.e. not yet collaborative). Additionally, the collateral for a loan does not function the way we think of a normal wallet working. You effectively deposit your collateral once, and if all goes well through the course of the loan, you redeem it at the end once the loan is paid off. There were many fewer interactions than what we expect from a modern wallet, to say nothing of collaborative custody. Our code reflected this domain, centering around the idea of “escrows”: an address represented an escrow, which backed a loan, whose wallet was simply a collection of escrows, each a redeem script built from public keys generated from a set of extended public keys.
As our product, and the world of multisignature wallet standards, began to mature, that static one-dimensional model started to show its age. But even the emerging standards left much to be desired for what we needed to build on. The descriptor standard, for example, is too broad to map clearly to the underlying domain: It can represent anything from a private key to a complex multisig watch-only arrangement, and requires unexpected complexity to, ironically, "describe" a standard wallet. The internal accounting of “receive” and “change” descriptors, xpubs, and redeem scripts, is even more complex.
Effectively, the problem we have is that all of the domain language in bitcoin was invented by developers and mathematicians and did not reflect the businesses and interactions built on top of it. This is fine for an exchange, which can abstract away most of that complexity into a user interface interchangeable with that of a traditional bank. For the universe of collaborative custody, however, this is woefully insufficient, since the bitcoin-specific domain is inevitably surfaced to users. We were developing systems that had to incorporate this bitcoin-specific complexity into financial services involving custom permissions, wallet backups, key replacements, recoveries, state management, and relationship-based shared ownership, while minimizing interactions with hardware devices and maximizing security and peace of mind. We needed to invent language that was both more descriptive and more approachable.
Let’s zoom out just a little bit more and talk about when a coffee mug is a donut. In the field of mathematics known as topology, this joke is used to illustrate how two seemingly different things that share certain properties can have a certain equivalence, known as homeomorphism.
But of course they’re not the same thing. So how you look at a particular domain model and what properties are considered can have a deep impact on the complexity that model can support.
Consider the concept of a wallet in bitcoin. As discussed, in the early days a wallet was simply a random collection of public and private key pairs: N descriptors, where N is the number of addresses in the wallet. With HD wallets, we introduced the idea of a relationship between these keys. These related groups of keys could further have relationships, forming a tree-like structure: a master seed which can generate groupings for different networks (mainnet, testnet), different accounts, and different purposes (change, receive): K descriptors, where K is the number of groups, one descriptor for each group, and N addresses per group.
Multisig expands this complexity one step further, where you pull in extended public keys from multiple seeds, creating a relationship between them that has to be maintained. And with collaborative custody we now have a relationship between keys between wallets, e.g. in the case of a key replacement, where a new wallet is created using two keys from a previous wallet, and some continuity of transaction history should be preserved.
Thinking of a wallet as just a collection of public keys (or grouping of public keys as in a multisig address) is clearly insufficient to distill the complexities of collaborative custody, because despite what the mathematicians might tell you, a coffee mug is not a donut.
To build out our new topology, we’ll start with strands: the extended public key that each member of a collaborative custody arrangement must bring. This is usually sourced from an HD seed phrase, though it doesn’t have to be. We refer to this xpub as a strand because of the linear relationship of public keys that can be generated from it. We could think of these strands as coming from the user's spool.
Once we’ve collected each of the strands from our participants, we can combine them to form a braid. This is probably the most important concept in the model because it encapsulates a few important characteristics of the domain.
Consider that when you braid several strands together, the strands become inextricably linked. According to BIP67, the order of their derived public keys will change as you move along the braid, but if you’re one foot along the braid, you are the same distance along each strand, just as when you move along the indexes of a multisig descriptor you should be incrementing the index of each member xpub from which you generate the corresponding public key. Another nice characteristic is that a braid has greater tensile strength than its constituent parts individually (greater security in multisig!).
Next, let’s consider how an address might be represented by a braid. One important thing to keep in mind about bitcoin addresses is that they aren’t real. They are abstractions that are easier for users to understand and verify. More accurately, they are serializations of information that represent a locking script for a transaction output. In multisig, this script is a combination of public keys and how many of their private keys are required to sign. As far as the blockchain is concerned, there is no relationship between different locking/redeem scripts generated from the same braid. But for us, this relationship is very important, as is a host of other interwoven information about that address. This is what helps for backups, balance management, and recoveries, for example. In collaborative custody, keeping track of this is also what allows users to use their wallet with an external coordinator like Caravan or Sparrow without disrupting operations on our platform.
To encapsulate this information, we envision moving along the braid to the relevant index and taking a cross-section or a slice. This slice presents a very specific topology of the relationship between the strands at that point in the braid: these are the public keys from each strand at that index. The topology is an important point because if you change the order of these keys, you’ll get a different redeem script (and therefore address). If you don’t keep track of this order (or have a way to generate it), then you increase the likelihood of lost funds.
So what does this give us? Well now we have a language that more clearly represents the relationships between the various objects.
A wallet is made up of two braids, one for external deposits (“receive braid”) and one for internal ones (“change braid”). Currently at Unchained, each braid is made up of three strands. These strands don’t have to have a prior relationship (i.e. they can come from different owners or key agents), but once associated in a braid, that is effectively permanent. To determine an address for any given braid, we simply move to the appropriate index and take a slice. This slice provides a unique set of public keys from each strand all at the same distance along their length. These public keys are ordered in a specific way to create a deterministically recoverable redeem script, from which we can derive the address. This way of slicing the braid also represents the one-time-use nature (or expected use) of any given slice, as the physical act of slicing is an irrevocable process.
Another advantage of this model worth mentioning is that it provides some future flexibility by decoupling from some of the more rigid and less descriptive elements of BIP32 path standards. For example, by separating script types (e.g. P2SH, P2WSH, and P2TR) from their keys, we can make it easier to reason about and execute upgrades to newer script types. For a similar reason, this model lends itself well to other less-explored extensions of the BIP32 space such as blinded xpubs, which you could think of like a blinded strand, an image that is a bit more evocative of how the protocol actually works.
Finally, we also have a much more robust framework in our data model for thinking about balances. Consider how we can reflect not just the balance of a wallet or an address, but of a strand, a collection of braids, an individual braid, or even strands across multiple braids generated by the same key!
Below, you can see a full visualization of this model for thinking about collaborative custody.
Oftentimes in complex systems, whether you like it or not, the business domain will make an impact on your code. Either through increased complexity when you fight its emergent nature or through the benefits you gain when the code reflects those real world patterns.
When Unchained was founded back on block 433179, our business domain was much narrower and the multisig standards landscape less mature. As the product evolved to meet the demands of our clients and fill needs we saw in the market, this loan-based “escrow” model struggled to scale. It became increasingly clear that the resulting code complexity was unsustainable, but the challenge was not just to come up with a more robust language to describe the new reality but also to migrate our backend operations in a way that brought minimal to no client disruptions.
The resulting braid model and the code that came from it is the result of years of work from many talented engineers across several systems in our stack. This is also just the foundation, one that will allow us to build more complex, interoperable, extensible and scalable systems moving forward.
Where can we go from here? This idea of a topological domain model really shines in areas where you have to deal with non-interactive collaboration. What does this mean? Collaborative multisig is of course a perfect example of this where you have multiple key holders in a quorum. Once the key information is shared (i.e. the strands) each collaborator now has the ability to not just generate addresses but create spending transactions as well, all without interacting with their wallet partners. How we model this information dictates how well we can support this unpredictability. The model starts to shine even more when we talk about key replacements: When one of the keys is compromised and needs to be swapped, what should the topology of this change and the relationship between the before and after models look like?
Beyond multisig, bitcoin scripting is both restrictive and expressive, which lends itself well to this type of non-interactive, off-chain modeling. As the ecosystem starts to leverage these more, through advancements such as taproot, covenants, and vaulting and the introduction of new, more expressive tooling such as miniscript, the opportunity and need for comprehensive domain models will only grow stronger.
We expect other developers and bitcoin domain experts will find this to be a useful piece of their toolkit moving forward and look forward to how these ideas could be leveraged. If this is something you might be interested in collaborating on, reach out to us @unchainedcom or on our open source GitHub.