Skip to content
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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open

Taproot multisig #4159

wants to merge 14 commits into from

Conversation

andrewtoth
Copy link

@andrewtoth andrewtoth commented Sep 5, 2024

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

@andrewtoth
Copy link
Author

@prusnak @andrewkozlik @onvej-sl @matejcik Any feedback on this PR?

@matejcik
Copy link
Contributor

any feedback on this PR?

sure!
in general, we are interested in taproot multisig functionality.
we don't have it on the roadmap at the moment, so before anything happens on the PR, you'll need to wait for us to prioritize it.
personally I don't know the status of the various taproot multisig proposals, so i can't comment on whether your solution implements the one (or one of) that we want

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 🤷‍♀️

@prusnak
Copy link
Member

prusnak commented Sep 10, 2024

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.

Copy link
Member

@prusnak prusnak left a 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?

core/src/apps/bitcoin/common.py Outdated Show resolved Hide resolved
@andrewtoth
Copy link
Author

Thank you for the feedback @matejcik and @prusnak

in the future, please open an issue first

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 tr(key, multi_a(k, key1, ... keyn)) and also the BIP388 wallet policy tr(@0/**, multi_a(k, @1/**, ..., @n/**)).

Can we also add some tests that test the construction of a Taproot multisig transaction?

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?

@andrewtoth
Copy link
Author

@matejcik @prusnak thank you - I have updated the PR to use a deterministic HDNode for the unspendable internal pubkey, and added tests for getting addresses and signing and serializing a tx.

@prusnak
Copy link
Member

prusnak commented Sep 13, 2024

I have updated the PR to use a deterministic HDNode for the unspendable internal pubkey

Why is this preferred to using a static dummy key from previously used code?

@andrewtoth
Copy link
Author

I have updated the PR to use a deterministic HDNode for the unspendable internal pubkey

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.

@prusnak
Copy link
Member

prusnak commented Sep 16, 2024

added tests for getting addresses and signing and serializing a tx.

Amazing!

@prusnak
Copy link
Member

prusnak commented Sep 16, 2024

I fixed smaller issues detected by the CI in commit e8cc7c4, but there is still bigger fish to fry:

$ cd core
$ make pyright                
python ../tools/pyright_tool.py

Ignored 88 custom-defined errors from 17 files.

ERROR: We have issues!

/Users/stick/work/trezor/trezor-firmware/core/src/apps/bitcoin/sign_tx/bitcoin.py:661: - error: Expression of type "tuple[bytes, bytes]" is incompatible with return type "bytes"
  "tuple[bytes, bytes]" is incompatible with "bytes" (reportReturnType)

/Users/stick/work/trezor/trezor-firmware/core/src/apps/bitcoin/sign_tx/bitcoin.py:677: - error: Argument of type "int" cannot be assigned to parameter "pubkey" of type "bytes" in function "multisig_pubkey_index"
  "int" is incompatible with "bytes" (reportArgumentType)

/Users/stick/work/trezor/trezor-firmware/core/src/apps/bitcoin/sign_tx/bitcoin.py:682: - error: Argument of type "int" cannot be assigned to parameter "signature" of type "bytes" in function "write_witness_multisig_taproot"
  "int" is incompatible with "bytes" (reportArgumentType)

/Users/stick/work/trezor/trezor-firmware/core/src/apps/bitcoin/sign_tx/bitcoin.py:688: - error: Argument of type "int" cannot be assigned to parameter "signature" of type "bytes" in function "write_witness_p2tr"
  "int" is incompatible with "bytes" (reportArgumentType)

/Users/stick/work/trezor/trezor-firmware/core/src/apps/bitcoin/sign_tx/bitcoin.py:713: - error: Argument of type "int | bytes" cannot be assigned to parameter "signature" of type "bytes" in function "set_serialized_signature"
  Type "int | bytes" is incompatible with type "bytes"
    "int" is incompatible with "bytes" (reportArgumentType)

/Users/stick/work/trezor/trezor-firmware/core/src/apps/bitcoin/sign_tx/decred.py:168: - error: Expression of type "DecredSigHasher" is incompatible with return type "SigHasher"
  "DecredSigHasher" is incompatible with protocol "SigHasher"
    "hash341" is an incompatible type
      Type "(i: int, tx: SignTx | PrevTx, sighash_type: SigHashType) -> bytes" is incompatible with type "(i: int, tx: SignTx | PrevTx, sighash_type: SigHashType, leaf_hash: bytes | None) -> bytes"
        Function accepts too many positional parameters; expected 3 but received 4 (reportReturnType)

/Users/stick/work/trezor/trezor-firmware/core/src/trezor/utils.py:282: - error: Expression of type "memoryview[_I@memoryview] | memoryview[int]" is incompatible with return type "memoryview[int]"
  Type "memoryview[_I@memoryview] | memoryview[int]" is incompatible with type "memoryview[int]"
    "memoryview[_I@memoryview]" is incompatible with "memoryview[int]"
      Type parameter "_I@memoryview" is invariant, but "_I@memoryview" is not the same as "int" (reportReturnType)

/Users/stick/work/trezor/trezor-firmware/core/src/trezor/utils.py:292: - error: Expression of type "_I@memoryview | int" is incompatible with return type "int"
  Type "object* | int" is incompatible with type "int"
    "object*" is incompatible with "int" (reportReturnType)

/Users/stick/work/trezor/trezor-firmware/core/src/trezor/utils.py:300: - error: Expression of type "_I@memoryview | int" is incompatible with return type "int"
  Type "object* | int" is incompatible with type "int"
    "object*" is incompatible with "int" (reportReturnType)

Found 9 issues above
make: *** [pyright] Error 1

@andrewtoth
Copy link
Author

For me

$ cd core
$ make pyright  

returns

python ../tools/pyright_tool.py
Error: [Errno 2] No such file or directory: 'pyright'
make: *** [Makefile:211: pyright] Error 1

I couldn't find much docs on setting up pyright. Any hints?

@prusnak
Copy link
Member

prusnak commented Sep 16, 2024

I couldn't find much docs on setting up pyright. Any hints?

Hm. Not sure - maybe npm install -g [email protected]?

Or you can use pyright from brew, if you are no macOS.

@andrewtoth
Copy link
Author

Thanks! After my last commit I get:

python ../tools/pyright_tool.py

Ignored 88 custom-defined errors from 17 files.

SUCCESS: Everything is fine!

@andrewtoth
Copy link
Author

I get an error that I need to add a changelog as well. Should I do that or will you?

@prusnak
Copy link
Member

prusnak commented Sep 16, 2024

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

@andrewtoth
Copy link
Author

Ah I see legacy tests are failing. I only tested this on Model T with core. Can this feature be disabled on legacy devices, or is legacy support required for this?

@prusnak
Copy link
Member

prusnak commented Sep 16, 2024

Can this feature be disabled on legacy devices, or is legacy support required for this?

We can disable the tests with

@pytest.mark.models(skip=["legacy"])

@andrewtoth
Copy link
Author

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?

@andrewtoth
Copy link
Author

@prusnak I'm unsure how to proceed here. I don't have any context about what the Post comment with UI diff URLs jobs are and why they are failing. There's also a Changelog check job failing that seems to be looking for a legacy changelog but there hasn't been anything touched in legacy.

Is there anything else I need to do here?

@prusnak
Copy link
Member

prusnak commented Sep 20, 2024

@prusnak I'm unsure how to proceed 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 legacy/ folder (added extra parameter to some functions). Again, let's have the firmware team to either add a dummy changelog entry or find a way how to suppress the change.

Again, thank you for this massive contribution and we'll take it from here.

crypto/tests/test_check.c Outdated Show resolved Hide resolved
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",
Copy link
Author

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.

Comment on lines +101 to +127
for i in multisig.address_n:
node.derive(i, True)
return node.public_key()
Copy link
Contributor

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?

Copy link
Author

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?

Copy link
Contributor

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]

Copy link
Author

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?

Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

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

I don't think it makes a difference in terms of security and privacy for the first 3 options.
Only the chain code and public key are used to derive the actual public key that will be used in the script though, so the last option would introduce malleability. For example, two xpubs where one lies about the depth would produce the same redeem script but would result in a different chaincode for the dummy xpub.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I didn't realize but if using just the 33 bytes compressed public key like I have here it does make it compatible with Liana, if Liana has their xpubs sorted, no duplicates, and max depth of 2.

Copy link
Contributor

Choose a reason for hiding this comment

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

I can proceed with a BIP and mailing list post if this is acceptable.

Thanks. Unless @onvej-sl has any objection to the proposed method, I encourage you to go ahead.

Copy link
Contributor

Choose a reason for hiding this comment

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

I have no objections.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for your patience. I have created a BIP draft specifying this behavior. Once it gets assigned a number I can rebase this and hopefully we can see it merged 🤞

bitcoin/bips#1746

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.

Implement multisig for Taproot
6 participants