Notes on incomplete transactions

Roger Taylor
7 min readAug 20, 2020

ElectrumSV only stores fully signed transactions. We should handle and store incomplete transactions. What would this entail? I have no idea and a lot of other stuff to work on. But I am going to write up some thoughts while I have them in mind, so I can refer back to them later. I’m on the tail end of the lead up to the ElectrumSV 1.3.5 release, so why not..

If you have any thoughts on this subject, please feel free to share them in the comments, or on either of Unwriter’s Slack, or the Metanet.ICU Slack. Do you have any relevant use cases that we need to cover? Please let us know.

One multi-signature scenario

I do not know all the different ways in which people use ElectrumSV, but occasionally someone says “Here’s what I do, it doesn’t work any more.” So I test what they said they did, and sure enough it errors. The bugs are then fixed, and then I use variations on that previously unknown to me situation to test releases from that point.

Wallet actors

The user’s team has three co-signers for their mutual multi-signature wallet of which only two of the three need to sign. These three cosigner wallets are only accessed with no internet connection. A fourth wallet that has no signing ability but has watch-only access to the mutual wallet is used to both create the initial transaction and finally when it is signed to broadcast it.

Why don’t we illustrate this with screenshots? This won’t be a complete guide to how a new team would set up the same situation, as the focus of this article isn’t to be such a guide but to rather use this case as an example of incomplete transactions.

Step 1: Create the transaction in the watch-only online wallet

First we create a new empty wallet and we add a multi-signature account.

Select the multi-signature option in the account wizard.

Then we configure it for the correct number of cosigners.

We ensure that out of 3 cosigners a minimum of 2 signatures are required.

Then we configure the master public key for each cosigner.

Enter the cosigner keys for each cosigner.

On finishing the creation, the account is synchronized. This wallet has been used before so there is a funding transaction and two spending transactions.

This account is properly created, having been used helps see this.

So now the online watch-only wallet is ready to start the payment process. Note that there is no “Send” button.

The Send tab for a watch-only account.

Having filled in the fields, the user clicks “Preview”.

The incomplete payment transaction.

Note that the “Status” is “Unsigned” and the transaction dialog does not offer either the ability to “Sign” or “Broadcast” the transaction. So the user clicks “Save” to save the incomplete (not fully signed) payment transaction as a JSON file — maybe to a USB drive to hand to the first cosigner.

Saving the unsigned payment transaction to disk.

Step 2: Sign the transaction in a first cosigner wallet

Having received the incomplete payment transaction from the user in step 1 on a USB drive, the first of the cosigners who will sign it receives it. They load the transaction file from the disk.

Loading a transaction from a file on disk.

This opens the same unsigned transaction in the transaction dialog.

Viewing the loaded unsigned transaction.

We can see the transaction has not been signed by anyone. The “Sign” button is enabled. There are a lot of possible improvements here that could be made, but these will be ignored for now, as.. priorities. The cosigner clicks the button.

The now (partially) signed viewed transaction.

The first cosigner then clicks “Save” and saves the partially signed transaction to disk to give to the second cosigner.

Step 3: Sign the transaction in the second cosigner wallet

Having received the incomplete payment transaction from the cosigner in step 2 on a USB drive, the second of the cosigners who will sign it receives it. They load the transaction file from the disk. This opens the partially signed transaction in the transaction dialog.

The (partially) signed transaction loaded from disk.

We can see the transaction has already been signed by the first cosigner. The “Sign” button is enabled. There are a lot of possible improvements here that could be made, but these will be ignored for now, as.. priorities. The second cosigner clicks the button.

The now fully signed transaction.

Now the “Status” is “Signed”. Ignore the “Broadcast” button being enabled, this is a computer in airplane mode not ElectrumSV running in offline mode. It won’t work.

The second cosigner then clicks “Save” and saves the fully signed transaction to disk to give to the online watch-only wallet user.

Step 4: Broadcast the transaction in the watch-only online wallet

Now the watch-only wallet user has the fully signed transaction. They load it from disk.

The externally signed transaction loaded.

Note that the “Status” is now “External signed transaction”. The “Broadcast” button is enabled, so they click that and the payment is made.

Analysing the scenario

ElectrumSV only retains information about complete transactions. While it can mark unspent coins as allocated if it needs to, it has no way to currently know where and what for.

This leads to some observations from the use case above.

The offline cosigners are only doing dumb signing

They know what key they have to use to sign the transaction, so they can just sign the transaction and add the signature to the transaction. You can see this because the input (the coin being spent) has not been identified as theirs and is not highlighted green in steps 2 and 3.

Offline cosigners have no idea of what transactions have been signed, and in this use case will not necessarily see complete (fully signed) transactions unless they were the last cosigner.

The offline cosigner wallets have no record of what they signed

A cosigner is not guaranteed to see all of the fully signed transactions, or even a majority of them, and after adding their signature does not retain any information about either signing it or the transaction. We should provide a better user experience here, but an interesting thing to note is that this is an extreme version of the model all wallets should be using — P2P.

A quick recap of how P2P is related to this: Rather than broadcasting a complete transaction to the BSV network, the user gives the complete transaction to the recipient of the payment or some agent acting on their behalf. And they can provide extra information like the transactions the spent coins (in the inputs) came from, and proofs that they belong in any given block.

We could bundle state like this into the saved incomplete transaction file, to be passed to the cosigner. But how well will this really work? We have no idea what any given cosigner participated in, and what state they already have. Either the watch-only wallet provides all the state with every transaction, or there needs to be some form of communication where a cosigner requests the missing parts from the watch-only wallet user — who is the only participant that has it all. And considering that that communication will be by USB drives, you can see why the dumb model is more user friendly.

Let’s ignore that USB drives have their own safety concerns.

Chinese malware from the manufacturers or dropped drives for suckers?

Holding devices up to each other and scanning QR codes is probably safer. Does anyone want to implement QR code movie-based data transfer for ElectrumSV? Or some commonly supported method of nearby Bluetooth data exchange?

Data exchange by QR code movies.

The offline cosigning case is again, an extreme case. We should make it user friendly, and try and keep it in mind. But it isn’t the main focus of any work to better handle incomplete transactions.

Back to incompleteness

Most of ElectrumSV’s transaction state is stored under the transaction hash. The transaction hash is the SHA256 hash of the transaction data. This is relevant for incomplete transactions because an incomplete transaction has to change to become complete and each change alters the hash. So we cannot store incomplete state directly with complete state.

If the incomplete state we are aware of at this point is mostly signing context, then it is also relevant for complete transactions. You want to see who signed what input in a transaction both when the transaction is incomplete and when it is complete— that Dolly, Jolene, Kenny, Rogers all participated in it and how. This has two possible implications, the first that we have a working identity system that all participant’s wallets share, and the second that the signing coordination needs to bundle additional data. Because this will likely happen online, there can be bidirectional requests to fill in gaps, which the offline cosigning will be unable to do.

This article does not aim to be a solid reference for incomplete transactions. It’s just to note some thoughts down so I can recover them later when I can move forward on this in some way. If you have any thoughts or suggestions for what might be relevant, please let us know via the options listed at the top of the article.