Page MenuHomePhabricator

[electrum] fix pubkey sorting when signing multisig
ClosedPublic

Authored by PiRK on Oct 5 2023, 16:23.

Details

Reviewers
Fabien
Group Reviewers
Restricted Project
Commits
rABC55ef2a9e70c4: [electrum] fix pubkey sorting when signing multisig
Summary

Transaction.get_sorted_pubkeys used to sort keys in place in the coin dict, so when Keystore.can_sign checked whether the signature in the same position as its xpub was not None, it could assume that the pubkeys were already in the same order as the sigs.

When we switched from using Transaction.get_sorted_pubkeys(coin_dict) to using TxInput.get_sorted_pubkeys() in most places, while letting some code still work with coin dicts, we broke the highly undocumented assumption that pubkeys would be sorted "in-place" at the time of signing the transaction. Keystore.can_sign could return False if some signature was placed in the position in which its pubkey was mistakenly located (the signatures were always put in the correct order by Transaction.sign)

Also Transaction._sign_txin updated the pubkey in the TxInput based on its sorted index, when it was actually not sorted in-place.

As a result, multisig signing was sometimes broken depending on the order in which the process of creating and signing the transaction happened. The keystore would sometimes mistakenly assume that it already had done its signing job, and the pubkeys were foobared (one pubkey duplicated, one missing).

Fix this by:

  • using TxInput in keystore and accessing the xpubkeys via TxInput.get_sorted_pubkeys, so we don't need to assume that previous code did the sorting
  • only updating the pubkey in the very specific case where it is required, which happens to be when there is only a single pubkey
  • sorting the pubkeys in TxInput.to_coin_dict, just in case there is some other piece of code still using coin dicts (Transaction.inputs()) and making the same assumption.
Test Plan

Send multiple transactions with a multisig setup, making sure that the order of the involved pubkeys varies compared to the order of the keystores (I patched the transaction dialog with a print(tx.txinputs()[0].pubkeys to verify).

python test_runner.py

Diff Detail

Repository
rABC Bitcoin ABC
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

PiRK retitled this revision from [electrum] use TxInput in keystore and fix keystore.get_tx_derivation for multisig to [electrum] fix pubkey sorting when signing multisig.
PiRK edited the summary of this revision. (Show Details)
PiRK edited the test plan for this revision. (Show Details)

sort keys in update_pubkey so tx._sign_txin does not need to do any more assumptions

PiRK published this revision for review.Oct 6 2023, 10:02
Fabien requested changes to this revision.Oct 6 2023, 12:01
Fabien added a subscriber: Fabien.

sorting _pubkeys and _x_pubkeys in place in TxInput

Let's not do this unless there are a ton of things relying on it. It's better to explicitely sort when needed, there are not a ton of signatures in a tx anyway and it's self documenting.

This revision now requires changes to proceed.Oct 6 2023, 12:01
PiRK edited the summary of this revision. (Show Details)
PiRK edited the test plan for this revision. (Show Details)

don't order pubkeys in place, don't update pubkeys when it is not needed or not safe to do so.

This diff incidentally makes get_sorted_pubkeys return lists intead of tuples, which makes my life easier when when checking the sorting (sorted(...) returns a list). I think this is an improvement, but I can revert and adapt the new test if reviewers don't agree or want it to be in its own diff.

Fabien added inline comments.
electrum/electrumabc/keystore.py
86 ↗(On Diff #42579)

while you're at it, it's a useless comment

This revision is now accepted and ready to land.Oct 6 2023, 14:13

remove redundant comment and undo unrelated change in test (I intended to reuse the first change address, but ended up using the first receiving address for this test)