← index

Private Key Handover

An archive of delvingbitcoin.org · view original topic →

ZmnSCPxj jxPCSnmZ · #1 ·

Subject: Private Key Handover

Introduction

There are protocols where, at the end of the protocol, semantically, some lump fund which was previously shared by two or more participants, is now owned by only one of the participants.

For example, an onchain HTLC starts with two participants: the offeror and the acceptor. Assuming the “happy path” where the preimage is learned by the offeror, at the end of the protocol, the offeror semantically releases its claim on the HTLC funds and the fund (should) now be singularly controlled by the acceptor.

When implementing such a protocol onchain, on a blockchain that supports Taproot, a possible optimization is for the protocol to always require ephemeral public keys in the keyspend path, and for participant(s) that want to release their claim on the fund to simply hand over the ephemeral private key to the final single beneficiary, which would allow the beneficiary party to use the keyspend path to claim the funds without further participation of any other party.

In our HTLC motivating example, the keyspend path of the HTLC would be the MuSig2 of the ephemeral keys of the offeror and acceptor, and in the happy path, the acceptor gives the offeror the preimage directly (instead of onchain) and the offeror then hands over the ephemeral private key. The acceptor, now in possession of both halves of the ephemeral private key of the keyspend path, can now use the keyspend path unilaterally.

Benefits And Limitations

To be very clear, in a world where MuSig2 exists and is implemented, the advantage is:

We should note the limitation as well:

Private Key Handover

The “private key handover” is a building block for Bitcoin protocols:

For an HTLC protocol to be used as a building block for a swap protocol:

The following fallback cases need to be handled:

Of note is that, as the keys in the keyspend are ephemeral, and by “ephemeral” we mean “will only be used for this run of the protocol”, this gets forward security once the spend of the fund is deeply confirmed.

Graceful Implementation

Initial implementations of a protocol that uses private key handover need not immediately support RBF or batching.

Thus, initial implementations of the protocol can be simple, naive implementations that just spend the lump sum output to an address with unilateral control of the beneficiary, in a simple one-input-one-output unbatched transaction with a feerate slightly higher than prevailing feerates.

The ability to add RBF or batching can be added later once the implementors have enough confidence in their work for the simple use-case, to pursue more ambitious sophistication.

Importantly, implementations with RBF and/or batching support can talk seamlessly with simpler implementations without that support; the protocol remains the same, but there is no need for the simple implementation to be aware of additional messages or anything needed by the sophisticated implementation.

This allows for “graceful implementation” where you can start with a simple implementation and add features, without breaking protocol compatibility, and also start off with a simpler protocol that will work at both the “proof-of-concept” scale to “mass adoption champagne problems” scale.

Particularly sophisticated implementations can even batch the spending of the lump sum with completely unrelated, other protocols, which themselves may or may not use private key handover, saving even more blockspace.

Compare this to the complexity of Lightning Network splicing. Even initial implementations that have no intention of supporting RBF or batching, must be at least aware of the messages that are needed if the counterparty has to RBF or batch. Indeed, initial implementation are forced to implement RBF, as multiple versions of the splice transaction need to be signed in parallel until one of the versions is deeply confirmed (so you might as well just go whole hog and be able to propose the RBF yourelf, since you need to keep track of multiple exclusive transaction versions either way).

Encrypted Handover

We should be cautious about sending private keys across the network.

Of course, one layer of protection is to use BOLT8, or the less advanced protocol TLS, to send messages containing the private key. This provides end-to-end encryption and message integrity.

However, this is only one layer of protection. In particular, the encryption and integrity ends at the point in the software where the BOLT8 decryption and integrity validation is done.

Modern software can be very complex and may send various information to various sub-components. Plaintexts of the messages after the end-to-end encrypted integrity tunnel would be sent around and possibly have multiple copies in transit from one part of the software to the part which actually implements the protocol that uses private key handover. Additional copies of private keys would be problematic as it increases the surface area for private key exfiltration.

As a concrete example, the C-Lightning (also known as CLN or Core Lightning) implementation will send out plaintexts of odd-numbered messages, via pipes, to any plugins that register for notification of odd-numbered messages. Partial security breaches may allow hackers to get at in-pipe data, and various ways of running CLN plugins remotely may allow the odd-numbered messages to be sent via JSON-RPC notifications, in plaintext, over the network.

To reduce this attack surface, our protocol can send over the encryption of the ephemeral private key, as follows:

This scheme reduces the scope of where the plaintext private key is available, providing extra layers of protection in contexts where it would be desirable.

(The proposed scheme is only safe under the random oracle model.)

While many implementations would not need to have the protection against internal breaches of data, putting it as part of the protocol allows the rare implementation that does need it to be safer.

The encryption sub-protocol requires primitives (SECP256K1 scalar by point multiplication, 33-byte DER formatting, SHA256) that are likely to be commonly needed in Bitcoin software anyway, so the additional implementation complexity of using encrypted private key handover is expected to be low.

Non-HTLC Examples

Private key handover is a generic building block for protocols. It can be used beyond HTLCs.

For example, in the theoretical SuperScalar design, a client may cooperatively exit from the LSP, by offering the entire amount inside the SuperScalar for some equivalent onchain amount. The client can use private key handover for the keys it used inside the SuperScalar in the cooperative exit protocol; once the server has given an onchain contract that the client can use to atomically swap its in-SuperScalar funds for the onchain funds.

In particular, with possession of the client key after the client has exited, the LSP can now arbitrarily reallocate liqudiity towards other clients that happen to share part of the SuperScalar tree with the client that exited, giving the LSP extra flexibility in its liquidity management.

The only requirement is that the client use some kind of hardened derivation for the in-SuperScalar keys; because the keys are handed over on exit, the client must ensure that the handed-over private key cannot be used to derive their master private key. Obviously, as the private key would need to be used across client restarts, the client would need to store the derivation path for the in-SuperScalar key(s) in persistent storage.

Even outside of SuperScalar, a bespoke LSP-client protocol can allow for a form of cooperative exit where the LSP can sign the channel output fund unilaterally, in exchange for the equivalent amount of client funds in an onchain TXO. This allows the LSP to batch multiple cooperative exits:

If the LSP has enough onchain churn (e.g. if it is also operating other onchain businesses, such as a Lightning-onchain swap service, or operating as an exchange at enough scale to use onchain operations), the ability to freely batch the above operations with others can provide actual blockspace savings.

Again, the only requirement is that the client use some kind of hardened derivation for the channel signing keys.

Non-Taproot Usage

Of note is that while true MuSig2 signing, where there is no private key handover, is only possible with Schnorr signatures, if private key handover is done, you can use a MuSig2 sum of public keys with ECDSA.

After private key handover, the beneficiary can compute the corresponding private key of the MuSig2 sum. Thu, even if ECDSA is not linear, the beneficiary can still compute the equivalent private key anyway.

The major drawback for ECDSA usage is that there is no ECDSA address that also has Taproot. Thus, you would need to expose an additional public key in the single SCRIPT, which may very well cancel out any blockspace savings. Nevertheless, it is still quite possible to use private key handover for additional flexibility and the ability to RBF and batch arbitrarily, even without blockspace savings.

(It should be noted that the last example in the previous section — bespoke LSP-client cooperative exit protocol where the LSP gets the flexibility to batch with other operations — would, today, be using ECDSA.)

ZmnSCPxj jxPCSnmZ · #2 ·

Shared-spending Inter-Protocol Framework

A high-quality general Bitcoin-management (a.k.a. “wallet”) implementation can implement the following software interface as a framework by which additional code can implement protocols that are capable of sufficient flexibility to allow arbitrarily adding new transaction inputs and new transaction outputs.

As a concrete example, any protocol that uses Private Key Handover as described in the main text above can integrate with the below-described inter-protocol framework in order to create batched, RBFed transactions.

The framework maintains a (possibly empty) set of requested commands. A “command” in this context is either (1) a request to spend an input before some timeout or (2) a request to fund some address before some timeout.

In particular, end-users can simply call fund_output one-at-a-time and the framework will, if feasible, batch the outputs into a single transaction, i.e. instead of requiring end-user code to call into a send_multi-equivalent command (so that end-user code has to decide to batch before calling into the Bitcoin-management framework), the end-user code calls multiple fund_output commands, and the framework automatically batches them, using RBF to replace any broadcasted transactions.

The framework implicitly assumes that all inputs are some SegWit version, and thus have a non-malleable txid independent of signing. The framework simply bans non-SegWit inputs outright.

The framework has two entry points: fund_output and spend_input. Both accept one or more callbacks, which the framework uses to coordinate with the actual protocols.

For a simple “send to an address” base usecase, you can call fund_output with the amount to be sent, with trivial callbacks where requesterAddressCallback returns the target address, requesterReadyCallback trivially returns ok, and requesterDoneCallback notifies the user UI. The magic here is that if the human operator performs multiple “send to an address” commands in sequence, the wallet framework automatically batches them (it can delay its decision loop to wait for new commands to reduce the number of times it RBFs, but it can always RBF newer transactions for each new “send to an address” request). In addition, in case of a sudden surge in fees, the wallet framework can automatically RBF without further human operator input; further, the human operator can specify some maximum feerate, and then requesterAddressCallback can fail the request automatically if the feerate is higher than the maximum set by the human operator.

For the proposed “Private Key Handover”, any protocol that implements Private Key Handover proposed in the original post can call into spend_input, and provide a requesterSignCallback that uses the handed-over private key to sign the input it is claiming.

Of note is that obviously the framework needs to continue operating across restarts. Thus, the actual callbacks would not be some kind of in-memory representation of a function; instead, you would need to pre-register some set of protocols that can use this framework, with some kind of per-protocol name or identifier that is stable across restarts (for example, it can be a versioned string, such as simple_send_to_addr_v1). This registration for protocols that would call into fund_output could provide the function addresses for all the callbacks needed, so that the actual fund_output call accepts only the registered name.

The framework would store the pending requests on persistent storage, and on restart, would load and look for any pending requests, and check the stored protocol names against the pre-registered protocols (and abort the restart if the in-storage string does not match any pre-registered protocols). This make the framework a little harder to use, but makes it robust against restarts; the framework can record exactly where in its processing it has completed (e.g. if it has called some callback for some particular request). This may require that individual requests also have an identifier (such as a UUID) across restarts.