Skip to content

2fa mobile cosigner - #10979

Draft
ecdsa wants to merge 3 commits into
masterfrom
2fa_mobile_cosigner
Draft

ecdsa wants to merge 3 commits into
masterfrom
2fa_mobile_cosigner

Conversation

@ecdsa

@ecdsa ecdsa commented Sep 17, 2026

Copy link
Copy Markdown
Member

No description provided.

Comment on lines +131 to +143
def add_cosigner(config: 'SimpleConfig', *, xprv2: str, xpub1: str, xpub3: str, password: str) -> None:
"""Stores the keys of a 2fa wallet."""
assert password, 'the cosigner key must be encrypted'
xpub2 = keystore.from_xprv(xprv2).get_master_public_key()
cosigner = {
'xpub1': xpub1,
'xpub2': xpub2,
'xpub3': xpub3,
'xprv2': pw_encode_with_version_and_mac(xprv2.encode('utf8'), password),
}
cosigners = [c for c in get_cosigners(config) if c['xpub2'] != xpub2]
cosigners.append(cosigner)
config.PLUGIN_TRUSTEDCOIN_COSIGNERS = cosigners

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really blurring the boundaries of wallet vs config. It is abusing the config IMO.

Also, what if someone has multiple 2fa wallets?

@ecdsa ecdsa Sep 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really blurring the boundaries of wallet vs config. It is abusing the config IMO.

could be moved to a separate file. the point is that is should not be a wallet.

Also, what if someone has multiple 2fa wallets?

that's why cosigners is a list.

@ecdsa
ecdsa force-pushed the 2fa_mobile_cosigner branch 3 times, most recently from 8aada38 to 54fbfce Compare September 18, 2026 11:07
TrustedCoin is going to stop their cosigning service. To keep things running,
we shall use the Electrum mobile app as cosigner.
 - Wallets keep the '2fa' type, because two xprvs are derived from a seed.
 - desktop wizard (restore only): show xprv2, xpub1, xpub3 in a '2fa_cosigner:'
   QR code.
 - mobile wizard: scan that QR code (xpub1, xprv2, xpub3).
 - desktop shows the PSBT as a QR code after signing; that dialog closes once
   the wallet sees the transaction. Existing 2fa wallets can set up the mobile
   cosigner from the status bar icon; missing x3 is derived.
 - remove server client, TOS, OTP, billing and related hooks (tc_sign_wrapper,
   abort_send, get_tx_extra_fee, get_action/WalletUnfinished, is_sweep).

what is missing:
 - QR codes are size limited. We should implement animated QR or use nostr
 - not sure if we want a full wallet on the mobile app. We could as well
   store the (encrypted) xprv in the config file.
The mobile app no longer creates a wallet when it is set up as cosigner: the
keys of the wallets it cosigns for are stored in its config file, so the user
does not have to open a wallet in order to sign.
- the keys live in the config file, see electrum/cosigner.py: a cosigner is
  indexed by the fingerprint its key uses in transactions, and everything else
  is encrypted with the password of the device, so that reading the config file
  does not tell which wallets it cosigns for. The daemon re-encrypts them when
  that password changes, as this must not depend on a plugin being loaded.
  Setup fails without a unified password; if there is no wallet yet, the
  password chosen during setup becomes the password of the app.
- transactions are prefixed with '2fa:', so that the app looks up the keys in
  its config file instead of the current wallet. It rebuilds the wallet scripts
  from the 3 xpubs, and displays outputs, amount and fee before broadcasting.
  The derivations found in the transaction are not trusted: an output counts as
  change only if it pays to the script we recompute for it.
- the wizard has a 'Scan QR code' button when the device has no wallet.
- the signature of a non-segwit input does not commit to its amount, so a
  malicious transaction could make the cosigner burn coins as miner fees. QR
  codes do not carry previous transactions, so the mobile app fetches them
  from the network, and it refuses to sign a non-segwit input without one.
  Whether an input is segwit is decided by the script we recompute from the
  xpubs: txin.is_segwit() believes the witness field of the transaction.
- refuse inputs that already carry a witness or a scriptSig. Those fields
  decide how an input is signed, hence which sighash our key commits to.
- refuse non-default sighash flags: with SIGHASH_NONE or ANYONECANPAY our
  signature would not commit to the outputs, which could then be changed.
- refuse to sign if the fee is unknown. The previous transactions are now
  fetched with ignore_network_issues=False, so that a transaction is never
  displayed with amounts we could not verify.
@ecdsa
ecdsa force-pushed the 2fa_mobile_cosigner branch from 54fbfce to bc284d7 Compare September 18, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants