-
-
Notifications
You must be signed in to change notification settings - Fork 663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Taproot multisig #4159
base: main
Are you sure you want to change the base?
Taproot multisig #4159
Conversation
675c45c
to
7adcabd
Compare
7adcabd
to
20ad7e0
Compare
20ad7e0
to
14e9116
Compare
@prusnak @andrewkozlik @onvej-sl @matejcik Any feedback on this PR? |
sure! in the future, please open an issue first where you outline your proposed solution. that way you can avoid doing all the work in case we say it's not something we are interested in 🤷♀️ |
The change is not so big and honestly it is worth merging in. Of course, we really need to have the new code properly tested. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also add some tests that test the construction of a Taproot multisig transaction?
Thank you for the feedback @matejcik and @prusnak
There is in fact an open issue, #1946, which I linked. The consensus in that issue is to implement the approach in this PR. Also, this approach will be compatible with BIP387 descriptors
Yes, I'd be happy to add tests and make any changes needed to have this feature merged and released. But first, I would like some assurance that this will in fact be merged. Would we be able to approve the workflows here so I can make sure I didn't miss anything else? |
14e9116
to
c89ace9
Compare
Why is this preferred to using a static dummy key from previously used code? |
This makes it compatible with BIP388 wallet policies. I cannot make a multisig wallet using a Trezor as one key and another hardware wallet that uses wallet policies as another key with just a static dummy key. |
Amazing! |
I fixed smaller issues detected by the CI in commit e8cc7c4, but there is still bigger fish to fry:
|
For me
returns
I couldn't find much docs on setting up pyright. Any hints? |
Hm. Not sure - maybe Or you can use pyright from brew, if you are no macOS. |
Thanks! After my last commit I get:
|
I get an error that I need to add a changelog as well. Should I do that or will you? |
I added it in my commit already |
Ah I see legacy tests are failing. I only tested this on Model T with |
385baa6
to
a9ff3de
Compare
We can disable the tests with @pytest.mark.models(skip=["legacy"]) |
For the device tests, it appears I need to add hashes for each device and each language. Could that be done in an automated step by CI? |
eebf5ad
to
69e4e74
Compare
69e4e74
to
549d592
Compare
@prusnak I'm unsure how to proceed here. I don't have any context about what the Is there anything else I need to do here? |
Let's leave that to @matejcik or someone else from the firmware team. I am sure the fix is trivial, but for me or you it would take hours to figure out. Also legacy needs a changelog entry because we touched the code in the Again, thank you for this massive contribution and we'll take it from here. |
depth=0, | ||
fingerprint=2084970077, | ||
child_num=0, | ||
chain_code=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we also want to allow passing in the chain code to create a randomized xpub that still uses the NUMS point public key. That way the derived public key is provably unspendable, but cannot be fingerprinted on chain.
We will need to define a new parameter for this in the MultisigRedeemScriptType.
for i in multisig.address_n: | ||
node.derive(i, True) | ||
return node.public_key() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something I don't understand: The dummy pubkey is derived using multisig.address_n
. Suppose we have two wallets. The first one uses a node HD1
and the other a node HD2
. Suppose we have a multisig address created from public keys HD1/0/0
and HD1/0/1
. Since the wallets use different paths to derive the dummy pubkey, they will generate different addresses. Consequently, if the first wallet creates a transaction with the multisig address as an output, the second wallet will create a valid witness with a probability of 1/2, because the parity of the tweaked dummy key is used in the witness. Is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But address_n
must be the same for the entire MultisigRedeemScriptType
, so how can the address be created by HD1/0/0
and HD1/0/1
? That would imply an address_n
of [0,0]
for the first key and [0,1]
for the second. Am I misunderstanding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was under the impression that you could specify address_n
for each pubkey separately in multisig.pubkeys[*].address_n
.
return [multisig_get_pubkey(hd.node, hd.address_n) for hd in multisig.pubkeys] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see you can have pubkeys or nodes, and pubkeys have their own address_n individually. I haven't tested this with that. I suppose we can disable using pubkeys list for taproot multisig?
Is pubkeys list legacy or is it still widely used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, I'm going to do a quick research to see what internal keys other wallets that support Taproot multisig use. If we want other wallets to be interoperable with trezor, we should follow the same approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@darosior already implemented a deterministic derivation for the chaincode in Liana; it would be great if new implementations of the deterministic derivations are kept compatible.
I don't think it's great that it depends on the order of keys provided. If we want to sort the pubkeys beforehand and order the xpubs in the order of the derived pubkeys, that would make a different chaincode for each order.
I think we should sort the xpubs before hashing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's great to see that someone else came to essentially the same solution. Seems like we are on the right path. To make sure that we are all on the same page, let me summarize the current plan:
- The dummy XPUB that is used to derive the internal public key will use the NUMS public key specified in BIP 341 and a chain code deterministically derived from the multisig XPUBs.
MultisigRedeemScriptType
doesn't change. Trezor derives the dummy XPUB internally.- All the public keys in the multisig, including the internal public key will use the same derivation path.
- To keep things simple, I would disable
MultisigRedeemScriptType.pubkeys
for taproot multisig, i.e. enforce the use ofMultisigRedeemScriptType.nodes
. There is nothing stopping us from loosening this restriction in the future if necessary.
The question remains how to derive the chain code. This is tricky. Trezor doesn't deal with policies or descriptors directly at the moment, at least not for multisig. Right now, distinguishing between sortedmulti_a()
and multi_a()
would be up to the host application, and it's up to that application to fill in the MultisigRedeemScriptType
in the correct order. So if sortedmulti_a()
is specified, it needs to derive the public keys and sort the XPUBs accordingly in MultisigRedeemScriptType
.
It seems to me that we can't just take the order of the XPUBs given in MultisigRedeemScriptType
, because with sortedmulti_a()
it would make the dummy XPUB depend on the path, since the host changes the XPUB order for each path. That would presumably be incompatible with Ledger, not to mention confusing. So unless we overhaul MultisigRedeemScriptType
to be able to handle descriptors, we need the dummy XPUB derivation to be independent of the XPUB order. Meaning they need to be sorted somehow, as you proposed, or the derivation operation needs to be order-agnostic, e.g. dummy chain code = XOR of the multisig chain codes. The XOR is not great, because it allows a malicious participant to set the chain code to a desired value, but come to think of it, perhaps that not an issue.
All that being said, I'd very much hope for the solution to be compatible with existing implementations, and ideally with whatever will be the future specification, because sooner or later this will need to be standardized to ensure smooth interoperability amongst wallets. Hopefully your comment in delvingbitcoin.org will spark some discussion about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that we can't just take the order of the XPUBs given in
MultisigRedeemScriptType
, because withsortedmulti_a()
it would make the dummy XPUB depend on the path, since the host changes the XPUB order for each path. That would presumably be incompatible with Ledger, not to mention confusing.
Ledger's implementation doesn't care how the chaincode of the unspendable key is computed, as it's explicitly passed on by the client together with all the other xpubs. It does recognize it as unspendable as long as the pubkey is the standard NUMS point, and that's important for security - or the client could lie and put an actual spendable pubkey.
Using the deterministic derivation only matters for UX (the device could in principle skip showing it altogether in that case); this is currently left for future improvements to the wallet policies / miniscript UX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am somewhat disappointed that our comments in delvingbitcoin.org did not spark any feedback. The way I see it, we are practically facing these options:
- The host will supply the chaincode for the unspendable XPUB and the user will confirm it on Trezor. This has poor UX. As if multisig signing wasn't hard enough to use for laymen, we add yet another layer of complexity for them to understand.
- Design our own scheme that derives the chaincode for the unspendable XPUB from the multisig XPUBs independently of their order. Poor interoperability, e.g. with Liana. All we can do is hope that our design is appealing enough that others adopt it.
- Adopt the Liana scheme, which in my opinion is not the best choice.
If a standard derivation scheme arises in the future, that is not identical with our choice, we will need to maintain the old scheme somewhere in our code, which I would like to avoid if we can. We currently don't have the capacity to push the standardization process forward. @andrewtoth would you be willing to do that? Even starting a discussion on the Bitcoin mailing list could be a step forward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrewkozlik thanks for that summary. I agree, we should ideally have a BIP for this before merging here. #1 would be safest option in that sense since we can guarantee we will be forward compatible with any standard that emerges. But, the UX drawback is indeed unfortunate.
I do think there is already a risk with Trezor not showing all xpubs being used in a multisig address, for the same reason the dummy xpub needs to be shown. A single trezor user must take for granted all the other xpubs used in creating the multisig address shown. Ledger solves this by storing this information as a wallet policy that only has to be confirmed once.
I will be meeting with some of the other developers who have commented in that delving thread next week. I will be sure to discuss this with them.
Adds basic taproot multisig support.
Uses a single
OP_CHECKSIGADD
-based script at the root level. Uses the secp256k1 generator as the basis for an xpub that derives an unspendable internal pubkey.Resolves #1946