Inside a Vinteum Deep Dive: The Standards Behind Bitcoin Wallets

How well do we understand the standards our software depends on?

Edil Medeiros
Leonardo Lima
Edil Medeiros & Leonardo Lima
Published on October 15, 202615 min read
Inside a Vinteum Deep Dive: The Standards Behind Bitcoin Wallets

A hardware signer receives a PSBT, parses it, and produces signatures. A wallet library derives keys according to BIP 32, constructs output descriptors, and uses Miniscript to reason about spending conditions. Applications rely on these abstractions every day, often without their developers needing to examine the underlying specifications or implementations.

That's usually a good thing. Well-designed abstractions allow developers to build sophisticated systems without having to understand every detail of the components they depend on. But for developers working on security-critical Bitcoin infrastructure, there is considerable value in occasionally opening those black boxes.

What exactly is serialized in a PSBT, and how does an implementation represent and manipulate that information? Why does BIP 32 use HMAC instead of a simpler construction such as SHA256(key || data)? What does a descriptor actually represent? What guarantees does Miniscript's type system provide, and how do implementations enforce them?

These aren't questions about how to use the standards. They are about why the standards are designed the way they are, what assumptions they encode, and how those assumptions affect the software built on top of them.

We want more developers to be able to contribute to future Bitcoin standards.  As part of that effort, we brought a group of Bitcoin developers and researchers together at Casa21, Vinteum’s physical hub in São Paulo, from October 5 to 9 for another week-long Deep Dive, this time focused on wallets. The group included contributors with substantial experience developing wallet libraries, production wallets, DIY hardware signing devices, and Bitcoin payment infrastructure. They brought different areas  of expertise, and many already worked extensively with the technologies we planned to study.

For five days, we worked through the specifications behind modern Bitcoin wallets, following their evolution from hierarchical deterministic keys to output script descriptors, Miniscript, partially signed transactions, and hardware signing interfaces. When needed, we moved from specifications into source code to understand how the abstractions are implemented and used. We wanted to understand not only what problems each standard solves, but also how these standards fit together, which complexities they abstract away, and which problems remain the responsibility of wallet developers.

A recurring theme emerged: as the ways we hold and spend bitcoin have become more sophisticated, the community has developed shared languages and interfaces to make that complexity manageable.

Keys are easy. Managing them is not.

We started with BIP 32, Hierarchical Deterministic Wallets. The motivation is familiar to wallet developers: generating an independent private key is straightforward, but maintaining an ever-growing collection of independently generated keys makes backups and recovery increasingly cumbersome.

BIP 32 defines a deterministic hierarchy in which a single seed can be used to reproduce an entire tree of keys. Its extended keys also allow non-hardened public-key derivation, so a wallet can generate receiving addresses without having access to the private keys needed to spend from them. The standard solves an important key-management problem, but its construction is more involved than repeatedly hashing a seed.

BIP 32 uses HMAC-SHA512, chain codes, and carefully defined hardened and non-hardened derivation procedures. We spent considerable time unpacking the cryptographic reasoning behind those choices. One question was particularly productive: why not replace HMAC with something simpler, such as SHA256(key || data)?

Answering it took us back to the security properties of hash functions, message authentication codes, the formal definition of pseudorandom functions, and the construction specified in RFC 2104. We revisited length-extension attacks and examined a real-world example of what can go wrong when a hash function is used naively for message authentication.

We investigated the possibility of recovering a parent private key when the attacker knows both the corresponding extended public key (xpub) and a non-hardened descendant private key (e.g. a leaked child private key). That's not new to cryptographers familiar with BIP 32, but there's an important distinction between using a cryptographic construction correctly and understanding the security assumptions that make it secure. In particular, this mathematical trick implies one should take special protection mechanisms not only to seeds, but also to derivations.

BIP 32 defines how keys are derived, but it doesn't prescribe how to logically arrange them in a coherent wallet. So we moved to BIPs 43 and 44 which introduced conventions for organizing deterministic key hierarchies, and later standards such as BIPs 49, 84, and 86 established derivation conventions for different output types. These conventions are another piece of the interoperability puzzle. Two wallets can implement exactly the same cryptographic derivation algorithm and still fail to locate the same keys if they disagree on the paths to use. But knowing which keys to derive is only part of the problem.

Output descriptors: connecting keys to scripts

Knowing a wallet’s BIP 32 keys and derivation paths still isn’t enough to determine which outputs belong to it.

A key might be used in a Pay-to-Public-Key-Hash output, a native SegWit output, a multisignature construction, or a Taproot output. Different spending conditions may involve the same keys, and each construction has its own requirements for identifying and spending the corresponding coins.

A wallet therefore needs to keep track of how its keys relate to the scripts used in its coins. Output Script Descriptors provide a common way to represent that relationship. Descriptors are a domain-specific language for describing how Bitcoin output scripts are constructed. An expression such as pk(KEY) describes a Pay-to-Public-Key output, while wpkh(KEY) describes a Pay-to-Witness-Public-Key-Hash output. The KEY expression can refer to an individual public key or include BIP 32 extended keys, derivation paths, and key-origin information. This allows the descriptor to capture both the construction of an output and information about the keys involved.

During the week, we read through the descriptor BIPs, progressively examining the available constructions: legacy outputs, SegWit outputs, multisignature expressions, and eventually Taproot script trees. The goal wasn't to memorize the descriptor functions. We wanted to understand what a common representation makes possible. Without descriptors, wallet software could end up implementing separate logic for each combination of script type, key representation, and derivation scheme. Applications that need to recognize or construct the same outputs would then have to reproduce much of that logic.

With descriptors, much of this information can be expressed in a standard format and interpreted by a common parsing and evaluation layer. The complexity hasn't disappeared. Instead, it can be handled through a reusable abstraction with well-defined semantics. That is particularly valuable for wallet recovery and interoperability. Given a descriptor and the relevant key material, an application can derive the scripts and addresses it needs to monitor without having to infer how the original wallet mapped keys to outputs.

There is a useful software-engineering principle here: parsing a structured representation into a meaningful internal model is often preferable to repeatedly checking for special cases throughout an application. In this sense, descriptors do more than simplify wallet backups or address generation. They establish a shared vocabulary that different wallet implementations can build upon.

The original descriptor constructions are mostly templates for familiar classes of Bitcoin outputs. But what if we want to describe more elaborate spending conditions?

Miniscript: making spending conditions analyzable

Bitcoin Script can express conditions involving multiple signatures, hash preimages, absolute and relative timelocks, and logical combinations of these primitives. The difficulty is not simply producing a valid script. Wallet software must also understand what information can satisfy it, what those satisfactions cost, and whether the construction has problematic properties.

This is where Miniscript, described in BIP 379, becomes interesting. We spent much of the second day working through its specification and returned to it on the fourth day to examine implementations in Bitcoin Core, Sipa's Miniscript library, and rust-miniscript.

One of our livelier discussions was about how to think of the Miniscript relation to output descriptors. Miniscript can be understood as a language in its own right, with its own fragments, composition rules, and type system. It can also be understood as extending the vocabulary available within output descriptors.

A descriptor such as wsh(...) can contain a Miniscript expression, letting us describe much more elaborate spending conditions within the descriptor framework. From this perspective, Miniscript adds more expressive, structured script fragments to a language wallets already use to describe outputs.

The type system is what makes this especially powerful. Take the fragment pk_k(KEY). Its script representation places a public key on the stack but does not perform signature verification. Miniscript assigns it the base type K. The wrapper c: adds the corresponding signature-check operation, transforming a suitable K fragment into one of type B, which produces a Boolean result. Thus, c:pk_k(KEY) represents the familiar <KEY> OP_CHECKSIG construction.

The distinction matters because putting a public key on the stack is not the same as requiring a valid signature. Miniscript's type rules prevent us from composing fragments in ways that violate the expected semantics of the surrounding expression. The type system goes considerably further. In addition to its base types, Miniscript tracks properties describing stack behavior, satisfaction and dissatisfaction, timelock interactions, and witness malleability. These properties enable tools to check how fragments may be combined and to rule out constructions that are syntactically expressible but unsuitable for the intended analysis or execution.

They also make it possible to reason systematically about spending paths. Given a Miniscript expression, software can analyze possible satisfactions, identify the signatures and other information they require, estimate witness costs, and check relevant execution limits. This gives developers more to work with than an opaque sequence of Bitcoin Script opcodes.

There is also a useful distinction between representing a contract and designing one. Suppose the intended spending policy is that Alice and Bob must cooperate, except that Alice may spend alone after a timeout. A higher-level policy language can express this intention without specifying the exact script construction. A compiler can then translate the policy into Miniscript, considering alternative implementations, their costs, and their satisfaction properties.

We examined the policy compiler associated with Sipa's Miniscript work, as well as the implementation and examples in rust-miniscript. This revealed a useful hierarchy of abstractions: a high-level policy expresses the intended authorization conditions; Miniscript provides an analyzable representation of those conditions; and Bitcoin Script provides the actual execution semantics. It also revealed places where the available documentation did not seem to fully explain the implementation. We'll return to those questions at the end of the post.

PSBTs: when constructing and signing are separate responsibilities

Not every Bitcoin transaction is constructed and signed by a single application with access to every required private key. Hardware signers deliberately separate key custody from transaction construction. Multisignature wallets require independent participants to contribute signatures. Privacy protocols such as Payjoin and CoinJoin go further, allowing multiple parties to collaborate on the transaction itself.

These workflows require a common representation of a transaction that may still be incomplete. That is the role of Partially Signed Bitcoin Transactions, or PSBTs. We studied BIP 174, which introduced PSBT version 0, and BIP 370, which specifies version 2.

A PSBT is not simply an unsigned transaction with signatures attached. It is a serialized structure containing key-value maps organized into global, input, and output information. These maps can carry previous output information, derivation paths, scripts, signatures, and other metadata participants need to construct and sign a transaction.

Understanding those details matter when implementing a signer or a wallet library. A device might routinely receive and sign PSBTs, while much of the work involved in interpreting their fields is handled by its parsing library. We wanted to look beyond that interface. One participant noted that BDK uses PSBTs in the internal flow of transaction creation and signing, even when not interacting with an external signer because that unifies the wallet procedures.

PSBT v0 is built around a consensus serialized unsigned transaction. This already establishes the transaction's inputs, outputs, and several other structural properties, while the surrounding maps work as decorators for inputs and outputs, carrying additional information needed to complete the transaction.

PSBT v2 changes that model. Rather than relying on a complete unsigned transaction embedded in the global map, it represents transaction-construction information through more explicit fields distributed across the PSBT's maps. It also provides mechanisms to express which modifications to the transaction remain permitted. The change is important for protocols where participants may need to modify a transaction rather than merely add signatures to an otherwise fixed structure. 

The format is only one part of the problem, however. A real wallet must coordinate participants, decide which information each participant needs, select signing options, validate modifications, and determine when a transaction is ready to be finalized. Even a library's API can influence how difficult this becomes. If a library supports PSBTs but does not expose the sighash choices or other fields required by an application, the application may need additional layers of logic or workarounds.

Our impression was that the PSBT specifications are not necessarily the most difficult part of implementing collaborative wallet functionality. Much of the complexity lies in the coordination and application logic built around them. Standardized data formats make this coordination possible across independent implementations. They do not prescribe the entire workflow.

Hardware signers and the limits of standardization

We also studied the Hardware Wallet Interface, or HWI, and the Rust-based BHWI implementation. We initially expected HWI to be something like a standardized communication protocol implemented by different signing devices.

HWI operates at a different layer. It gives wallet software a common interface for interacting with hardware wallets, while implementing support for each device's own protocol. One device may encode messages using Protocol Buffers; another may use a text-based protocol over a serial connection. Devices can also differ in their command sets and available transports. BHWI follows a similar device-specific model in Rust, but uses per-device interpreters that abstract transport and I/O to the caller.

The common interface is between wallet software and HWI or BHWI. It does not standardize how wallet software communicates with each physical signer. Adding support for a device therefore requires device-specific code in the interface layer, think of a device driver. Wallet applications using HWI can rely on its common API instead of implementing each device’s integration themselves.

This contrasts with descriptors and PSBTs. In those cases, standardization defines representations that independent implementations can exchange and interpret. HWI and BHWI instead provide interoperability by adapting between a common wallet-software interface and device-specific protocols.

Standardized formats and adaptation layers both help developers work across implementations without building every part of the stack themselves, but the engineering and maintenance costs are distributed differently. That difference becomes important when designing new devices, evaluating signer compatibility, or deciding where an application's responsibilities should end.

Following the abstractions into Bitcoin Core

By the final day, we had already covered all the initial list of topics for the Deep Dive, so we turned to Bitcoin Core's wallet, the reference for the whole ecosystem. After studying individual standards, we wanted to see how a mature wallet implementation brings them together.

We started at the createwallet RPC and followed portions of the initialization and persistence logic. We also examined the wallet database and its use of SQLite. One architectural choice caught our attention: despite SQLite’s relational model, the wallet uses it through a key-value storage abstraction.

Viewed in isolation, that might seem like an unusual design. But Bitcoin Core's wallet has evolved through numerous changes in key management, wallet models, storage backends, and compatibility requirements. The move away from Berkeley DB and the adoption of descriptor wallets are part of that history. An architecture that seems inelegant in isolation may represent a reasonable migration strategy for a project that must preserve existing wallets and users' funds.

The more interesting questions concerned the information a wallet must persist. Keys and descriptors are only part of its state. A wallet must also maintain a view of relevant transactions, identify outputs it controls, track spent and unspent coins, distinguish confirmed from unconfirmed transactions, and react to changes in the blockchain. How should that information be represented? What belongs in persistent storage? Which state can be reconstructed from the blockchain, and at what cost? Where should the boundary between a wallet and a Bitcoin node be drawn? These were questions raised by the wallet developers who joined us.

Although we discussed how wallets react to changes in the blockchain, we did not get far enough to study wallet synchronization in detail. Coin selection, fee estimation, and several other substantial wallet responsibilities also remained outside our scope. Still, looking at Bitcoin Core raised useful architectural questions, especially in the context of libraries such as BDK and proposals to build alternative wallet implementations using Bitcoin Core's interfaces.

It also brought the discussions closer to practical engineering work. Examining how applications integrate standardized components helped surface opportunities for improvement in existing wallet and payment infrastructure, including BTCPay Server subsystems. Some of these questions may lead to code changes. Others may require more substantial architectural investigation.

That's an important aspect of how we invite people to the Deep Dive gatherings: the objective is not limited to understanding the code in front of us. It is also to discover where that understanding can guide future work on other projects.

Standards as a shared foundation

Looking back over the week, the different specifications form a coherent progression.

  1. BIP 32 establishes how keys can be derived deterministically.
  2. Derivation-path conventions organize those keys so different implementations can reproduce the same hierarchy.
  3. Output descriptors connect keys to the scripts in which they participate, providing a common representation of the outputs a wallet is interested in.
  4. Miniscript extends that representation to more sophisticated spending conditions and adds a type system that supports meaningful static analysis.
  5. PSBTs define a common representation for exchanging incomplete transactions and signing information among independent participants.
  6. HWI provides a common software interface for wallet applications to interact with devices that use different communication protocols.

These standards, conventions, and interfaces each address a particular dimension of wallet development. Together, they help establish boundaries between responsibilities that would otherwise have to be implemented repeatedly and often incompatibly.

This has consequences beyond reducing duplicated engineering work. Common representations allow wallet libraries, signing devices, merchant infrastructure, and higher-level applications to evolve more independently. Developers can concentrate on balancing different tradeoffs and use cases on their projects while relying on shared semantics for the components they integrate.

But a standard does not automatically make the system behind it simple, nor does using a standard imply understanding its guarantees. That is where reading specifications and implementations together becomes valuable. A specification tells us what behavior is expected. An implementation shows us the decisions required to make that behavior concrete, including assumptions, optimizations, and limitations that may not be obvious from the public API. 

Sometimes the two perspectives fit together neatly. Sometimes they reveal questions that deserve further investigation.

Questions worth pursuing

As with our previous Deep Dives, the week left us with several questions that extend beyond what we initially planned to study. Some concern Miniscript itself. While reading implementations, we encountered internal type properties and analyses, including properties denoted by m and x, that are not specified among the type modifiers in BIP 379. These may simply be internal implementation details rather than missing normative elements of the language. Nevertheless, we found it difficult to locate additional documentation describing the design rationale for some of these analyses and how they fit into the broader Miniscript framework. A more systematic account of the analyses Miniscript enables (type checking, satisfaction construction, witness cost estimation, resource limits, and malleability properties) could help both new implementations and researchers interested in formal reasoning about Bitcoin contracts.

Another question concerns the higher-level policy language. Although the Miniscript compiler provides an implementation and examples of a language for expressing spending policies, we did not find a standalone normative specification for that language. BIP 388 (Wallet Policies for Descriptor Wallets), despite its name, addresses a different problem: compactly representing wallet policies based on descriptors, particularly for use with hardware signers.

Clarifying the distinction between these languages, documenting its semantics, and investigating the guarantees of policy-to-Miniscript compilation all seem worthwhile directions. At a higher level, Miniscript's compositional structure invites questions about how much we can formally establish about Bitcoin spending conditions. Can we better characterize all possible satisfaction paths? Can we build tools that explain how spending capabilities change as signatures, preimages, or timelocks become available? Can we verify more properties of the compilation process itself?

These questions also point to a broader challenge: making Miniscript better known and more widely adopted by protocol designers. Even projects implementing sophisticated spending conditions often construct their scripts directly. LND uses handwritten Script templates for Lightning contracts, while OpenSwap builds its swap contracts directly from opcodes. Miniscript could help developers specify and analyze contracts more systematically, rather than repeatedly implementing their own script construction and satisfaction logic. However, Miniscript cannot express every possible Bitcoin Script, and protocols such as Lightning have requirements that extend beyond individual spending conditions. Understanding these limitations, investigating whether Miniscript's expressiveness can be safely extended, and encouraging its adoption wherever applicable are worthwhile directions for the community.

Other questions concern wallet architecture and interoperability. How much information should a descriptor-based wallet need in order to reconstruct its state? Which parts of wallet functionality can be separated behind stable interfaces? Could Bitcoin Core's evolving IPC interfaces enable different wallet implementations to reuse its blockchain validation and notification capabilities without depending on its existing wallet architecture?

We do not claim that all these questions are unsolved research problems. Some may already have answers scattered through implementation code, mailing-list discussions, or documentation we have not yet found. Others may require engineering rather than research. But identifying those distinctions is itself a useful outcome to steer effort.

Opening the black boxes

We came into the week already familiar with the technologies we planned to study. Several participants develop and maintain software and firmware that depends on them every day. What we had not done was sit together and examine the collection of standards as a whole: their motivations, cryptographic assumptions, data structures, composition rules, implementation choices, and the interfaces connecting them.

That is a different exercise from learning how to call an API or implement a feature. It requires questioning design choices we have grown accustomed to, comparing mental models across projects, and occasionally discovering that something we thought we understood was considerably more subtle.

Some of the most valuable discussions began with questions that could easily be dismissed as implementation details. Why this cryptographic construction? Why this serialized representation? Why this type rule? Why does a wallet need to store this information? Why is the boundary between two components drawn here rather than somewhere else?

Those questions are important because the reliability of Bitcoin infrastructure depends not only on correct implementations, but also on developers understanding the assumptions and guarantees of the systems they build upon. No LLM can replace human judgement and that is part of what we aim to cultivate through Vinteum's Deep Dives. Funding gives contributors time to work on Bitcoin. Creating opportunities for experienced developers and newer contributors to examine complex systems together helps build the technical mindset needed to decide what to work on, how to approach it, and which assumptions deserve closer scrutiny.

We left the week with a more connected understanding of how the standards fit together and new questions that may lead to improvements in specifications, implementations, and wallet architecture. The standards have made increasingly sophisticated Bitcoin applications possible. Understanding them deeply is part of making sure those applications remain reliable as their complexity continues to grow.

#Deep Dives
Share