PSET: Constructing Confidential Transactions
by Andrew Poelstra
This is the second in a series of posts about our recently-released Elements 0.21. In our last post, we discussed Taproot, how it differs in Elements versus Bitcoin, and what our deployment strategy is for Liquid. Since then, Taproot has been activated on Bitcoin! Activation on Liquid will be about two weeks after all functionaries have been updated, which we hope to complete early next month. In this post, we are going to shift our discussion from consensus improvements (which we will return to in our next post, about new opcodes), toward wallet development.
In Bitcoin, the most widely-deployed protocol used by hardware wallets to communicate with host machines, and by multisigning wallets to communicate with each other, is Partially Signed Bitcoin Transactions (PSBT). More recently, Blockstream researcher Andrew Chow has championed the PSBT2 protocol, an upgrade to allow parties to cooperatively construct a transaction, starting from zero, a feature needed for PSBT to be useful when opening Lightning Network channels.
In Elements, PSBT2 is insufficient to construct transactions because our transactions are potentially significantly more complex: they include Confidential Transactions (CT), multiple assets, pegs in and out of the network, and asset issuances. To handle this additional complexity, we have extended PSBT2 to a new protocol, Partially Signed Elements Transactions (PSET). This protocol is the subject of this post.
The “Easy” Stuff: Pegs and Issuances
The first set of extensions to PSBT2 that we needed were to support the three new transaction types in Elements: peg-ins, peg-outs, and asset issuances. In fact, peg-ins and issuances are new types of transaction input, while peg-outs are a new type of transaction output. This means that, in theory, these new objects can be combined in arbitrary ways within a single transaction.
Because pegs and issuances are normative parts of the transaction, rather than witness data, they must be available before signing and before the transaction can be considered fully constructed. PSBT2 is defined in terms of several roles, and the role responsible for including such data is called the Creator. Since adding an asset issuance or peg is not conceptually any different from adding an input or output, the required changes were simply to add extra data fields to PSBT2 and document that the Creator is responsible for filling them in.
As it happens, PSBT has an in-built mechanism for adding extra “proprietary fields” for exactly this purpose. Each additional field is keyed by
0xFC, indicating a proprietary field, followed by the text
pset, and then its actual field ID.
Multi-Asset and Confidential Transactions
Our initial expectation was that supporting multiple assets and confidential transactions would be no more challenging than supporting pegs or issuances. After all, assets are merely a new output field, the existing amount field can be modified to be confidential, and range proofs and surjection proofs complete the set. Four output fields, no new input fields, done.
Unfortunately, in practice, adding fields was far from the end of the story. Immediately after adding these fields, we realized that we had no good answer to the question “who is responsible for filling them in?”. One answer is the Creator, since blinded outputs are not that different from unblinded ones. But this leaves much to be desired: the Creator role is not expected to have access to potentially-necessary data associated with inputs or outputs (if such data is needed, it is the role of the Updater to provide it), and most importantly, the Creator is not expected to have access to any secret key data, which blinding requires.
An additional complication is that in PSBT2, there may be multiple Creators who are independently constructing different parts of the transaction. But CT requires all the different blinding factors to add up to zero, which presents a coordination problem (even if you ignore the difficulty in learning the blinding factors of the inputs, which are not publicly available).
With these considerations in mind, the Creator role is inappropriate. The only role assumed to have access to secret data is the Signer, but this role is inappropriate as well, since the Signer role assumes the transaction is complete and all necessary information is present, neither of which is true during the blinding process.
On the other hand, the Creator does have some role to play in the blinding process. The Creator knows the original addresses of the outputs it is adding and can therefore extract the blinding key from these addresses and attach them to the outputs. The presence (or lack thereof) of these blinding keys is how other participants can determine which outputs need to be blinded and which ones do not
We therefore needed to add a new role, the Blinder, whose job it is to blind the transaction after the Creator and Updater have done their part, but before the Signers produce any signatures. Then to allow multiple Blinders to coordinate, we introduce the following rules:
- Every Blinder has a “blinder index,” indicating some input that this Blinder owns; each Blinder is responsible for blinding every output labeled by this index.
- After blinding, the Blinder adds up all the blinding factors it used for outputs, subtracts the blinding factors of inputs that it owns, and attaches the result to a new global field of the PSET. The final Blinder is responsible for making sure that these “scalar offsets” all add up to zero.
Of course, as always, the same participant may take on multiple roles, and if a Creator and Blinder are the same participant, they may do both at once and add a fully blinded output to a transaction without first attaching any explicit data.
But this leads us to the next problem with blinding: how can we determine if two partially blinded transactions are “the same.” In PSBT2, transactions are identified by their “unique ID,” which is computed as the txid of the transaction that would be created if the current set of inputs and outputs were to be used. But since blinding an output causes its explicit amount to be replaced by a confidential one, blinding changes this txid. This means that if two participants blind different parts of a transaction and then try to merge their results (formally done by the Combiner role, implemented in Bitcoin and Elements as the
combinepsbts RPC), they will fail, since it appears that the underlying transactions are not the same.
This is more than an academic concern: once a Blinder has replaced an explicit amount with a blinded one, there is actually no way to tell (without some extra secret data) that the blinded amounts and assets are the same as the unblinded ones. In many workflows this is okay (if everyone is responsible for blinding their own outputs, and everyone agrees on the (explicit) fee amount) but in others it is not. And regardless of whether it is “okay” to be unable to verify all the transaction data, users would still like
combinepsbts to work.
The solution to both is to add new types of proofs, explicit value proofs, to the PSET, which demonstrate that confidential values commit to given explicit values. We require that, when both explicit and confidential values are present (for either amounts or assets), a proof linking them also be present. In that case, we use the explicit values when computing the “unique ID” of the PSET, even though the confidential values will be used in the extracted transaction. This lets blinders prove that they are behaving correctly, if they need to, without revealing any blinding factors or other secret data which would complicate the security analysis.
As an aside, these new proof types did not require us to develop any new crypto — our existing rangeproofs can be constructed with a “range” of 1 value, and our asset surjection proofs can be constructed with a single input. The resulting proofs will do the job.
As a final observation, after implementing all this, we discovered that in some use cases, we actually do need to provide blinding factors, at least for transaction inputs, so that the Blinder role is able to make the entire transaction balance. In response, we have updated our development branch of Elements to add new PSET fields, though we have not yet deployed them. The lesson is that, even after spending a year working through all the different ways that PSET can be used, we still have not covered all of them!
The first steps toward PSET were included in Elements 0.18 in the form of the new
blindpsbt RPC call. At the time, we believed that we could implement it by simply extending PSBT with new fields, adding an extra RPC call to fill them in, and calling it a day. While this was a workable solution for somebody thinking of PSBT as a set of RPC calls, this was far from usable as a full-fledged protocol, and left many important user questions unanswered.
In Elements 0.21, we tackled these questions head-on and quickly realized that we needed to think carefully about how transaction blinding would interact with the rest of the protocol. We recognized that since blinding modifies the actual outputs of a transaction, we would need to base our new protocol on PSBT2, rather than the original PSBT which assumes that all wallets are working with a fixed transaction. We realized that blinding did not fit into any of the existing PSBT roles and that we would need to add a new one. We realized that blinding had the potential to modify a transaction in a way that was not visible to all counterparties and added extra proofs for user cases where this needed to be prevented.
We have included the culmination of this work in the latest version of Elements, but it is still not complete. As we gain feedback from external wallet developers and try to implement these protocols in our own products, we are finding new fields to add to support different use cases.
We encourage developers reading this to try the new protocol for themselves, and to reach out by email, IRC (#sidechains-dev on Libera), or Telegram to help us further gather data points and identify areas of improvement.