5

Trying to conceptually understand why I must store the public keys of the n hardware wallets in an x of n multisig set up? Only x of the seed phrases/private keys are needed to reconstitute/recover the multisig wallet, but why do I need all n public keys?
My understanding is that to send btc I only need x private keys (aka the x wallets on hand), to receive I just need the resultant multisig public key stored, but to recover I need x private keys and n public keys? Why is this?

2 Answers 2

5

The conditions for spending a coin (UTxO) are expressed using a small script. In order to be able to recover your coins, you need to back up both:

  • The script template
  • The private key(s) corresponding to the public key(s) used in the script

You need the script template because the script expressing the spending conditions is part of the coin itself. And the wallet needs to be able to locate which coins you own from the set of all existing ones (the "UTxO set").

This is true for any type of script (single-sigs, multisigs, or even more complex constructions). But it is often implicitly assumed backups with no corresponding script template are for a single-sig. The wallet would therefore reconstruct a single-sig script template from your private key.

It is however not possible to reconstruct any other script than a single-sig only from a private key. Hence the recommendation to back up other public keys involved in the multisig along with your own private key. This is simply a manner of backing up the script template to be able to locate your coins. (It's however a very incomplete manner.)

Backing up the public keys as a way to reconstruct the script template is suboptimal because it's leaving out a lot of information:

  • What's the script type? Is it using a bare script, a P2SH, a P2WSH, a P2TR?
  • What's the construction used? Is it using CHECKMULTISIG, a list of CHECKSIGs? A list of CHECKSIGADDs?
  • If using Taproot, in what leaf is the multisig? Is it spanning multiple leaves?

Modern Bitcoin wallets nowadays use Output Script Descriptors as a way of describing all the information needed to reconstruct a script template. I recommend using it to backup your multisig (or anything else) instead of relying on implicit information for something so critical.

4
  • I don't fully follow. 1. Would the Output Script Descriptor contain the derivation paths or would it be kept separate? 2. In the case of a multisig from 2 hardware wallets & 1 seed signer would there be a single OSD for the multisg wallet? Or 3 individual OSDs, one for each multisig constituent?
    – Runeaway3
    Sep 17, 2023 at 0:06
  • 1
    1. It's part of the descriptor. 2. Yes. It looks something like multi(2,xpub1/*,xpub2/*,xpub3/*) (not technically correct but simplified so you get the gist). Sep 17, 2023 at 10:41
  • And how would this be generated? If I self create the multisig via sparrow, is there an automated mechanism to generate this OSD?
    – Runeaway3
    Sep 17, 2023 at 16:28
  • You would generate one (or your wallet would) before receiving funds, and derive addresses from it. I'm not sure but i think you can access your descriptor from within Sparrow. Sep 18, 2023 at 0:23
3

The common practice in Bitcoin is to use "pay-to-script-hash" for multisig. In fact that is the only way to receive money in a standard "address" for your multisig wallet (there are variants in non-SegWit world, SegWit v0 and Taproot).

Pay-to-script-hash means the sender doesn't know where the money is being sent to. All it knows is that it is sending it to a "hash" of the actual script that specifies the spending conditions -- in this case a multisig script that looks like 2 <key1> <key2> <key3> 3 OP_CHECKMULTISIG.

To spend any pay-to-script-hash UTXO you have to provide the actual script first -- which the Bitcoin verification process will hash and check. And then (in the same transaction) you provide the signatures that will fulfill the script.

For example, this transaction is sending 1631068 satoshis to a 2-of-3 multisig address, but you can't know that just by looking at it. All you can see is that it is paying to the hash a64224765fa40b1bb04a4e8f82d29ca580e2022e20750078b5c3bd610b9015c9.

However, since it was spent in this transaction you can see the script that it used. It is the last element in the witness stack -- 522102b4608d0bba8f2f5665648462819d4a0fe97a8012d386d88ebb4e1ddc93fba5af2102db19c6f47c04ac128a8d9015a75dc350e5291849f999d267598d019dcdf5cbae21030994d7f93c0fb81eeeae14cabe0dbc8a547a9770aca304f4633eaab743fbc06a53ae, which translates (using bitcoin-cli decodescript or hal script decode) into:

OP_PUSHNUM_2 
OP_PUSHBYTES_33 02b4608d0bba8f2f5665648462819d4a0fe97a8012d386d88ebb4e1ddc93fba5af 
OP_PUSHBYTES_33 02db19c6f47c04ac128a8d9015a75dc350e5291849f999d267598d019dcdf5cbae 
OP_PUSHBYTES_33 030994d7f93c0fb81eeeae14cabe0dbc8a547a9770aca304f4633eaab743fbc06a 
OP_PUSHNUM_3
OP_CHECKMULTISIG

Finally, if you check the sha256 hash of the (byte-encoded) script above you'll see that it matches the script hash from the previous transaction.

>>> b = bytes.fromhex('522102b4608d0bba8f2f5665648462819d4a0fe97a8012d386d88ebb4e1ddc93fba5af2102db19c6f47c04ac128a8d9015a75dc350e5291849f999d267598d019dcdf5cbae21030994d7f93c0fb81eeeae14cabe0dbc8a547a9770aca304f4633eaab743fbc06a53ae')
>>> hashlib.sha256(b).hexdigest()
'a64224765fa40b1bb04a4e8f82d29ca580e2022e20750078b5c3bd610b9015c9'
1
  • You could perhaps explicitly answer the question of the asker by mentioning that you can only provide the actual script if you know all public keys. E.g. you could append “If you don’t know all public keys, you cannot reconstitute the actual script.” to the third paragraph.
    – Murch
    Oct 19, 2023 at 16:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.