achow101
commented at 10:32 pm on March 18, 2024:
member
This PR implements MuSig2 descriptors (BIP 390), derivation (BIP 328), and PSBT fields (BIP 373) so that the wallet can receive and spend from taproot addresses that have keys involving a MuSig2 aggregate key.
The libsecp musig module is enabled so that it can be used for all of the MuSig2 cryptography.
Secnonces are handled in a separate class which holds the libsecp secnonce object in a secure_unique_ptr. Since secnonces must not be used, this class has no serialization and will only live in memory. A restart of the software will require a restart of the MuSig2 signing process.
DrahtBot
commented at 10:32 pm on March 18, 2024:
contributor
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
DrahtBot added the label
Wallet
on Mar 18, 2024
DrahtBot added the label
CI failed
on Mar 19, 2024
DrahtBot
commented at 2:45 am on March 19, 2024:
contributor
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.
Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
Leave a comment here, if you need help tracking down a confusing failure.
in
test/functional/wallet_musig.py:128
in
a1e4c323dboutdated
144+ for deriv_path in dec_psbt["inputs"][0]["taproot_bip32_derivs"]:
145+ if deriv_path["pubkey"] in part_pks:
146+ part_pks.remove(deriv_path["pubkey"])
147+ assert_equal(len(part_pks), 0)
148+
149+ nonce_psbts = []
a1e4c323dbff9fa5095cf216d7cd528f10a1feeb: and that this is where round 2 happens (maybe link to the BIP at the top of the test and briefly summarise the steps)
in
test/functional/wallet_musig.py:158
in
a1e4c323dboutdated
161+ for wallet in wallets:
162+ proc = wallet.walletprocesspsbt(comb_nonce_psbt)
163+ assert_equal(proc["complete"], False)
164+ psig_psbts.append(proc["psbt"])
165+
166+ comb_psig_psbt = self.nodes[0].combinepsbt(psig_psbts)
a1e4c323dbff9fa5095cf216d7cd528f10a1feeb: because all wallets live on the same node, it’s useful to point out here that anyone, including non-participants can combine the partial signatures. Which is why the non-wallet combinepsbt and finalizepsbt RPC’s are used.
Sjors
commented at 2:22 pm on March 19, 2024:
member
Very cool stuff! Will review more later.
This pulls in (an older version of) the musig module in libsecp
What do you mean by “older”? Just that the PR to libsecp needs another rebase?
An open question is whether the approach for handling the secnonces is ideal and safe. Since nonces must not be reused, this PR holds them exclusively in memory, so a restart of the software will require a restart of the MuSig2 signing process.
It sounds safe, but not ideal, which might make it unsafe. Every Bitcoin Core instance involved would need to keep running, with the wallet loaded (and decrypted?) throughout the two rounds. For an airgapped setup with keys in multiple locations, the node in each location would have to be left running unattended (assuming one person running between them).
My understanding is that Ledger (cc @bigspider) creates a nonce, stores it, and then deletes it from storage as soon as it’s loaded (before signing). We could similarly store the nonce in our wallet and then delete the field at the start of the new round. For safety we could disable backups and dump RPC’s while a round is in progress (e.g. with a NO_BACKUP flag).
That only prevents accidental replay, not a replay attack, but it seems that anyone who is able to replay a node, already has access to its private keys (from the time a wallet was decrypted), so can’t do additional harm?
Implementation questions.
I tried making a 2 party tr(musig(A,B)) in a blank wallet. Initially I obtained two private keys and their public keys from another legacy wallet. I gave the new Alice wallet her private key and Bob’s public key, i.e. tr(musig(a,B)/0/*) but this failed with Ranged musig() requires all participants to be xpubs. Why though? Given that bip-musig2-derivation defines a virtual root xpub, and providers a fake chaincode, this restriction seems unneeded? (though it’s not blocker either, with descriptor wallets it’s easy to get an xpub - after #29130 anyway)
Once I had two wallets, I could see they generated the same receive address, nice! I then imported the same xpub/xpriv pair for the change address 1/*. I sent some (signet) coins to it, which arrived and confirmed.
Sadly after the GUI rugged me :-) Trying to send any amount elsewhere resulted in “Signing transaction failed” followed by “Transaction creation failed!”. Whereas I was hoping to get a PSBT this way.
Using the send RPC I do get a PSBT (from Alice). I had the musig2_participant_pubkeys set, but no musig2_pubnonces. That required calling walletprocesspsbt which seems an unnecessary extra step (but such fine tuning can wait). On Bob’s side the GUI complained with “Could not sign any more inputs”, but it did add a nonce.
At this point all the nonces were commited, so Bob could have added his partial signature. But at the stage the GUI crashes when trying to sign: [libsecp256k1] illegal argument: secp256k1_memcmp_var(&nonce->data[0], secp256k1_musig_pubnonce_magic, 4) == 0.
After a restart Bob’s walletprocesspsbt command didn’t fail. Which seems wrong: at this point the nonce should be gone, which he should complain about.
Starting with a fresh transaction, sing only the RPC I got the same crash, i.e.:
Alice: send
Alice: processpsbt
Bob: processpsbt
Bob: processpsbt: crash
Perhaps relevant: Bob’s wallet is encrypted, though it was unlocked throughout steps 3 and 4.
0% test/functional/wallet_musig.py
12024-03-19T14:23:33.113000Z TestFramework (INFO): PRNG seed is: 647071992440405417422024-03-19T14:23:33.115000Z TestFramework (INFO): Initializing test directory /var/folders/h6/qrb4j9vn6530kp7j4ymj934h0000gn/T/bitcoin_func_test_66knao3l
32024-03-19T14:23:35.070000Z TestFramework (INFO): Testing rawtr(musig(keys/*))
42024-03-19T14:23:35.192000Z TestFramework (ERROR): Unexpected exception caught during testing
(didn’t check if it’s the same crash)
bigspider
commented at 2:36 pm on March 19, 2024:
none
My understanding is that Ledger (cc @bigspider) creates a nonce, stores it, and then deletes it from storage as soon as it’s loaded (before signing). We could similarly store the nonce in our wallet and then delete the field at the start of the new round. For safety we could disable backups and dump RPC’s while a round is in progress (e.g. with a NO_BACKUP flag).
Not yet implemented, but that’s the plan: store nonces in flash memory (persistent memory) after generation; remove them from flash memory before signing starts (therefore, they’re gone even if there is a later failure, and signing must restart from nonce generation).
Note that there is no backup possibility for the persistent memory.
achow101
commented at 3:52 pm on March 19, 2024:
member
What do you mean by “older”? Just that the PR to libsecp needs another rebase?
I pulled in a commit that is probably outdated at this point. There may have been API changes since.
We could similarly store the nonce in our wallet and then delete the field at the start of the new round. For safety we could disable backups and dump RPC’s while a round is in progress (e.g. with a NO_BACKUP flag).
Disabling backups with a flag would not help as an oft suggested method for backing up a wallet is by copying the wallet file. There’s nothing that we can do about that, so to be safe, I don’t think we can store the nonces in the wallet file.
I tried making a 2 party tr(musig(A,B)) in a blank wallet. Initially I obtained two private keys and their public keys from another legacy wallet. I gave the new Alice wallet her private key and Bob’s public key, i.e. tr(musig(a,B)/0/*) but this failed with Ranged musig() requires all participants to be xpubs. Why though? Given that bip-musig2-derivation defines a virtual root xpub, and providers a fake chaincode, this restriction seems unneeded? (though it’s not blocker either, with descriptor wallets it’s easy to get an xpub - after #29130 anyway)
It’s specified in bip-musig2-descriptors that the musig must only contain xpubs if the aggregate will be derived from. I believe the rationale for this is that xpubs are intended to have derivation done on them whereas normal keys are not, and so there may be particular handling of such keys to deal with possibilities of derivation doing something unexpected, and so if we do anything with derivation, we should only use keys that are intended for derivation to avoid any confusion. I think @sipa was the one who made this suggestion.
Sadly after the GUI rugged me :-) Trying to send any amount elsewhere resulted in “Signing transaction failed” followed by “Transaction creation failed!”. Whereas I was hoping to get a PSBT this way.
The GUI may be expecting that at least one signature is produced, but we can’t do that with musig without at least one round with the cosigners. I have it implemented such that ProduceSignature does not report the tx as being signed until there is actually a signature, so even the partial sigs generation will not return “signed”.
After a restart Bob’s walletprocesspsbt command didn’t fail. Which seems wrong: at this point the nonce should be gone, which he should complain about.
Currently it just ignores if there is already a nonce for a participant’s key. It doesn’t replace the nonce, but it also doesn’t validate whether that key belongs to the wallet or whether the nonce exists in the wallet.
At this point all the nonces were commited, so Bob could have added his partial signature. But at the stage the GUI crashes when trying to sign: [libsecp256k1] illegal argument: secp256k1_memcmp_var(&nonce->data[0], secp256k1_musig_pubnonce_magic, 4) == 0.
…
Starting with a fresh transaction, sing only the RPC I got the same crash, i.e.:
Perhaps relevant: Bob’s wallet is encrypted, though it was unlocked throughout steps 3 and 4.
0% test/functional/wallet_musig.py
12024-03-19T14:23:33.113000Z TestFramework (INFO): PRNG seed is: 647071992440405417422024-03-19T14:23:33.115000Z TestFramework (INFO): Initializing test directory /var/folders/h6/qrb4j9vn6530kp7j4ymj934h0000gn/T/bitcoin_func_test_66knao3l
32024-03-19T14:23:35.070000Z TestFramework (INFO): Testing rawtr(musig(keys/*))
42024-03-19T14:23:35.192000Z TestFramework (ERROR): Unexpected exception caught during testing
(didn’t check if it’s the same crash)
Huh, works fine for me.
Sjors
commented at 4:37 pm on March 19, 2024:
member
Huh, works fine for me.
This was on Intel macOS 14.4 with a clean checkout and ./configure --disable-bench --disable-tests --enable-wallet --disable-fuzz-binary --disable-zmq --with-gui.
On Ubuntu 23.10 with gcc 13.2.0 the test do pass, odd.
(if this still happens after CI passes, I’ll dig a bit deeper, for now I’ll just test on Ubuntu)
I don’t think we can store the nonces in the wallet file.
Storing them in some other file might be fine too. As long as we delete it upon read, don’t sign anything if deletion fails and maybe also commit to some unique property of the PSBT.
Currently it just ignores if there is already a nonce for a participant’s key.
I guess we need to distinguish here between a nonce for our own key and one for other participants. We have no idea if some other node crashed. But it does seem reasonable to fail if we see a nonce for ourselves. Whether we previously crashed or if someone is trying a replay attack doesn’t really matter. Though it’s unusual for processpsbt to fail when called twice normally, here it seems justifiable.
Update: successfully completed the MuSig2 signing on Ubuntu!
achow101 force-pushed
on Mar 19, 2024
achow101 force-pushed
on Mar 20, 2024
achow101 force-pushed
on Mar 25, 2024
achow101 force-pushed
on Mar 25, 2024
achow101 force-pushed
on Mar 25, 2024
achow101 force-pushed
on Mar 26, 2024
DrahtBot added the label
Needs rebase
on Mar 29, 2024
achow101 force-pushed
on Apr 1, 2024
DrahtBot removed the label
Needs rebase
on Apr 1, 2024
Sjors
commented at 10:36 am on April 2, 2024:
member
Only 3 red CI machines to go :-)
achow101 force-pushed
on Apr 2, 2024
achow101
commented at 5:10 pm on April 2, 2024:
member
Sjors
commented at 8:05 am on April 3, 2024:
member
The test passes for me now on macOS.
DrahtBot added the label
Needs rebase
on Apr 6, 2024
achow101 force-pushed
on Apr 16, 2024
bitcoin deleted a comment
on May 23, 2024
bigspider
commented at 11:43 am on June 6, 2024:
none
Hi all,
an early alpha of the Ledger Bitcoin Testnet app with MuSig2 support is available for testing. (NB: the app is called Bitcoin Test Musig and not Bitcoin Test). It should be compatible with the latest draft of the specs.
Instructions and an easy end-2-end script for MuSig signing to play with it is available here for anyone interested in trying it out:
bigspider
commented at 6:09 pm on November 5, 2024:
These three constants are 0x1a , 0x1b, 0x1c in the final version of BIP-373.
achow101
commented at 8:11 pm on November 5, 2024:
Good catch! Fixed.
achow101 force-pushed
on Nov 5, 2024
fanquake referenced this in commit
80cb630bd9
on Nov 6, 2024
bigspider
commented at 11:37 am on November 6, 2024:
none
EDIT: this is now resolved.
The current implementation seems to be using the aggregate pubkey (before the tweaks) inside the key of the PSBT_IN_MUSIG2_PUB_NONCE (and I’d assume PSBT_IN_MUSIG2_PARTIAL_SIGNATURE, but I didn’t reach there, yet); instead, BIP-373 says that it must be the key found in the script and not the aggregate public key that it was derived from, if it was derived from an aggregate key. Therefore, I interpreted it as the taproot pubkey for a keypath spend, and the exact key that appears in the tapleaf for a scriptspend.
Using the aggregate key pre-tweaks could be problematic if the same aggregate key appears multiple times, for example in something like:
tr(NUMS/<0;1>/*,or_d(pk(musig(A,B)/<0;1>/*),pk(musig(A,B)/<2;3>/*)))
Here, the two musigs have the same participants, and same aggregate key pre-tweaks (and they are in the same leaf, so even the tapleaf_hash won’t help); only the tweaks allow the disambiguation.
Here’s what I pulled from the test I’m working on:
The aggregate_pubkey added by core is 03480793485c903f11b8ace2d81a89f4c0ec3b1944af98b6048426d35703f9898a, but I think it should be 02c94987d29e8939d728dd8879c8b5aa6c21b45c61a30751e394cafbef0e597f14 (after the BIP32-tweaks + the taptweak), matching the pubkey in the Script, if my understanding of BIP-373 is correct.
As a consequence, the musig2_pubnonces should probably use the name aggregate_pubkey_tweaked or something else that avoids confusion with the untweaked aggregate_pubkey that appears in musig2_participant_pubkeys.
lozanopo approved
achow101 force-pushed
on Nov 6, 2024
achow101
commented at 6:01 pm on November 6, 2024:
member
The current implementation seems to be using the aggregate pubkey (before the tweaks) inside the key of the PSBT_IN_MUSIG2_PUB_NONCE
Indeed, fixed.
Therefore, I interpreted it as the taproot pubkey for a keypath spend
I’ve interpreted (and implemented) it as also allowing the taproot internal key, not just the output key. I think that is actually what I meant when writing the BIP, but it’s been a while.
achow101 marked this as ready for review
on Nov 6, 2024
achow101 force-pushed
on Nov 6, 2024
achow101 force-pushed
on Nov 6, 2024
DrahtBot added the label
CI failed
on Nov 6, 2024
DrahtBot
commented at 8:09 pm on November 6, 2024:
contributor
Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the
affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
DrahtBot removed the label
CI failed
on Nov 6, 2024
bigspider
commented at 9:53 pm on November 6, 2024:
none
I’ve interpreted (and implemented) it as also allowing the taproot internal key, not just the output key. I think that is actually what I meant when writing the BIP, but it’s been a while.
I think it’s important that the (aggregate) plain key used in the key of PSBT_IN_MUSIG2_PUB_NONCE/PSBT_IN_MUSIG2_PARTIAL_SIGNATURE is unambiguous and clearly specified, or implementations will essentially have to try both in order to find in the PSBT all the pubnonces/musig_partial_signatures for a certain key.
Using the same public key that can be used to verify the final signature (therefore, the tweaked taproot pubkey for keypath spends, or the pubkey as it appears in tapleaves for script spends) seems the most natural choice to me.
I don’t have an opinion on PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS - I’m not using it, since it’s redundant in the context of signing based on BIP-388 wallet policies.
achow101 force-pushed
on Nov 6, 2024
achow101
commented at 10:46 pm on November 6, 2024:
member
Using the same public key that can be used to verify the final signature (therefore, the tweaked taproot pubkey for keypath spends, or the pubkey as it appears in tapleaves for script spends) seems the most natural choice to me.
That’s a good point, I don’t feel too strongly about this, it was just a bit more annoying to figure out how. I’ve updated the PR to do that.
bigspider
commented at 1:34 pm on November 7, 2024:
none
Looking good!
I can confirm that I was able to complete two e2e tests on regtest (commit 09a6091711eef299de7cfbfd340a112706422c81), using Ledger’s MuSig2 implementation for a cosigner and bitcoin-core for the other one.
In both cases, the Ledger device was just the MuSig2 cosigner, while bitcoin-core was both the other musig cosigner and the combiner/finalizer/extractor.
I will report more once I have more extensive tests.
bigspider
commented at 4:44 pm on November 7, 2024:
none
Attempting some fancier setups, I’m trying to do a “decaying MuSig” that starts as a 3-of-3 in the keypath, with 3 timelocked 2-of-2 in the scriptpaths. Not managing to work with the descriptor in core:
0$ ./bitcoin-cli -regtest getdescriptorinfo "tr(musig(tpubDD863BuWFdsaCg6f1SGdwLxp9mDcm3YRm3HxxbppBrizxvU1MqhQ1WpMwhz4vrZHNT7NFbXQ35CquVG9xaLsWaUWfSMZamDESisvtKZ7veF,tpubD6NzVbkrYhZ4YAPXpMw61GrdqXJJEiYhHo6wxVkfwZgUged5qXm6Df4NLf8ZTFXxW1UhxDKGeKdAVxZtmodC8KfR7SqmW6LGQfDGfnFLmQ6,tpubD6NzVbkrYhZ4WSLhjy9cMaGTEW7No4ALTRikqWo5xFsiTTkRfxA8eRyj2GTkFbkKU6HeW5z8LqrcgHbcVoVg2QZ8JECSHv4PpQ5vUdKJbkR)/1/*,{and_v(v:pk(musig(tpubDD863BuWFdsaCg6f1SGdwLxp9mDcm3YRm3HxxbppBrizxvU1MqhQ1WpMwhz4vrZHNT7NFbXQ35CquVG9xaLsWaUWfSMZamDESisvtKZ7veF,tpubD6NzVbkrYhZ4YAPXpMw61GrdqXJJEiYhHo6wxVkfwZgUged5qXm6Df4NLf8ZTFXxW1UhxDKGeKdAVxZtmodC8KfR7SqmW6LGQfDGfnFLmQ6)/1/*),older(12960)),{and_v(v:pk(musig(tpubDD863BuWFdsaCg6f1SGdwLxp9mDcm3YRm3HxxbppBrizxvU1MqhQ1WpMwhz4vrZHNT7NFbXQ35CquVG9xaLsWaUWfSMZamDESisvtKZ7veF,tpubD6NzVbkrYhZ4WSLhjy9cMaGTEW7No4ALTRikqWo5xFsiTTkRfxA8eRyj2GTkFbkKU6HeW5z8LqrcgHbcVoVg2QZ8JECSHv4PpQ5vUdKJbkR)/1/*),older(12960)),and_v(v:pk(musig(tpubD6NzVbkrYhZ4YAPXpMw61GrdqXJJEiYhHo6wxVkfwZgUged5qXm6Df4NLf8ZTFXxW1UhxDKGeKdAVxZtmodC8KfR7SqmW6LGQfDGfnFLmQ6,tpubD6NzVbkrYhZ4WSLhjy9cMaGTEW7No4ALTRikqWo5xFsiTTkRfxA8eRyj2GTkFbkKU6HeW5z8LqrcgHbcVoVg2QZ8JECSHv4PpQ5vUdKJbkR)/1/*),older(12960))}})"
1error code: -5
2error message:
3'and_v(v:pk(musig(tpubDD863BuWFdsaCg6f1SGdwLxp9mDcm3YRm3HxxbppBrizxvU1MqhQ1WpMwhz4vrZHNT7NFbXQ35CquVG9xaLsWaUWfSMZamDESisvtKZ7veF,tpubD6NzVbkrYhZ4YAPXpMw61GrdqXJJEiYhHo6wxVkfwZgUged5qXm6Df4NLf8ZTFXxW1UhxDKGeKdAVxZtmodC8KfR7SqmW6LGQfDGfnFLmQ6)/1/*),older(12960))' is not a valid descriptor function
Been staring at it for a while, it seems valid to me. Am I missing something?
Sorry if it’s obvious
achow101
commented at 4:51 pm on November 7, 2024:
member
musig() is not being parsed in Miniscript expressions yet.
bigspider
commented at 4:58 pm on November 7, 2024:
none
musig() is not being parsed in Miniscript expressions yet.
Ah, ok, I’ll keep an eye for updates.
Thanks!
achow101 force-pushed
on Nov 7, 2024
psbt: Implement un/ser of musig2 fieldsa6bcb764aa
rpc: Include MuSig2 fields in decodepsbt2f8617b2b3
psbt: Use specified sighash or SIGHASH_DEFAULT when dummy signing
When dummy signing for finalizing, use either the specificed sighash, or
SIGHASH_DEFAULT, rather than always SIGHASH_ALL.
For outputs, just use SIGHASH_DEFAULT.
c122152c15
desc spkm: Return SigningProvider only if we have the privkey
If we know about a pubkey that's in our descriptor, but we don't have
the private key, don't return a SigningProvider for that pubkey.
This is specifically an issue for Taproot outputs that use the H point
as the resulting PSBTs may end up containing irrelevant information
because the H point was detected as a pubkey each unrelated descriptor
knew about.
feb86f7e39
wallet, rpc: Only allow keypool import from single key descriptors
Instead of relying on implicit assumptions about whether pubkeys show up
or now after expanding a descriptor, just explicitly allow only single
key descriptors to import keys into a legacy wallet's keypool.
d1359eff3d
descriptors: Have GetPubKey fill origins directly
Instead of having ExpandHelper fill in the origins in the
FlatSigningProvider output, have GetPubKey do it by itself. This reduces
the extra variables needed in order to track and set origins in
ExpandHelper.
Also changes GetPubKey to return a std::optional<CPubKey> rather than
using a bool and output parameters.
719739aa39
descriptors: Move FlatSigningProvider pubkey filling to GetPubKey
Instead of MakeScripts inconsistently filling the output
FlatSigningProvider with the pubkeys involved, just do it in GetPubKey.
883d24d24d
descriptors: Have GetPrivKey fill keys directly
Instead of GetPrivKey returning a key and having the caller fill the
FlatSigningProvider, have GetPrivKey take the FlatSigningProvider and
fill it by itself.
GetPrivKey is now changed to void as the caller no longer cares whether
it succeeds or fails.
9e96339576
XOnlyPubKey: Add GetCPubKeys
We need to retrieve the even and odd compressed pubkeys for xonly
pubkeys, so add a function to do that. Also reuse it in GetKeyIDs.
636a59e221
spanparsing: Allow Const to not skip the found constant508302f0c4
sign: Add GetAggregateParticipantPubkeys to SigningProviderf020d406c0
Add MuSig2 Keyagg Cache class and functions
- MuSig2KeyAggCache contains a MuSig2KeyAggCacheImpl which has the
secp256ke_musig_keyagg_cache object to avoid having to link libsecp256k1
everywhere.
- GetMuSig2KeyAggCache creates the MuSig2KeyAggCache from a
std::vector<CPubKey>
- GetCPubKeyFromMuSig2KeyAggCache creates a CPubKey from a cache created
with GetMuSig2KeyAggCache
- MuSig2AggregatePubKeys does the two above functions together.
sign: Refactor Schnorr sighash computation out of CreateSchnorrSig
There will be other functions within MutableTransactionSignatureCreator
that need to compute the same sighash, so make it a separate member
function.
bbe4665d50
pubkey: Return tweaks from BIP32 derivationf94a9ed428
sign: Include taproot output key's KeyOriginInfo in sigdata0a4fcbf2b6
Add MuSig2SecNonce class for secure allocation of musig nonces2e3343b3ff
signingprovider: Add musig2 secnonces
Adds GetMuSig2SecNonces which returns secp256k1_musig_secnonce*, and
DeleteMuSig2Session which removes the MuSig2 secnonce from wherever it
was retrieved. FlatSigningProvider stores it as a pointer to a map of
session id to secnonce so that deletion will actually delete from the
object that actually owns the secnonces.
The session id is just a unique identifier for the caller to determine
what secnonces have been created.
c7a62f5c39
achow101 marked this as a draft
on Nov 7, 2024
achow101
commented at 6:23 pm on November 7, 2024:
member
Several earlier commits have been split out into separate PRs. See the tracking issue #31246 for the breakdown.
achow101 force-pushed
on Nov 7, 2024
sign: Add CreateMuSig2AggregateSig3dde12d319
sign: Add CreateMuSig2Nonce3f5699702c
sign: Add CreateMuSig2PartialSig029800f046
sign: Create MuSig2 signatures for known MuSig2 aggregate keys
When creating Taproot signatures, if the key being signed for is known
to be a MuSig2 aggregate key, do the MuSig2 signing algorithms.
First try to create the aggregate signature. This will fail if there are
not enough partial signatures or public nonces. If it does fail, try to
create a partial signature with all participant keys. This will fail for
those keys that we do not have the private keys for, and if there are
not enough public nonces. Lastly, if the partial signatures could be
created, add our own public nonces for the private keys that we know, if
they do not yet exist.
1a488142d4
wallet: Keep secnonces in DescriptorScriptPubKeyManb8bb007926
psbt: MuSig2 data in Fill/FromSignatureData5afc5d06f9
test: Test MuSig2 in the wallet3649c2eb20
achow101 force-pushed
on Nov 7, 2024
NicolasDorier
commented at 4:12 am on November 8, 2024:
contributor
Given BIP373 doesn’t have test vectors, it would be very useful that either this PR or the BIP include some hard coded PSBT examples to ensure every implementations are on the same page.
jonatack
commented at 12:54 pm on November 26, 2024:
member
Concept ACK
Given BIP373 doesn’t have test vectors, it would be very useful that either this PR or the BIP include some hard coded PSBT examples to ensure every implementations are on the same page.
Good point (the BIP373 test vector section currently states “TBD” and seems worth completing even if the implementation here also has tests).
Sjors
commented at 3:24 pm on November 26, 2024:
member
It might be useful if someone expands doc/multisig-tutorial.md to add a MuSig2 section. That doesn’t have to go in this PR, but it will make testing and review easier. The functional test added in this PR can be used for inspiration.
It can take advantage of the new <0;1> syntax and the new gethdkeys RPC.
I was able to generate a simple 2-of-2 tr(musig(A,B)/<0;1>/*) watch-only wallet on testnet4 and receive to it. Keys A and B were extracted from regular wallets by taking the pkh() account level xpub, including its origin.
However when trying to send walletprocesspsbt (with the wallet that has private keys) does not add any fields.
Sjors
commented at 9:49 am on December 3, 2024:
member
I also tried @bigspider’s MooSig demo which worked. I then crafted a multisig between A and the device: tr(musig(A,L)/<0;1>/*). I managed to register the policy (after several mistakes, it’s very tedious to do this manually).
I wanted to try using HWI to display the address, but I would have to modify it to work with the test app. I just yolo funded it.
I then created a withdrawal PSBT and pasted it in the Moosig script, modifying it to only add its public nonce. I also hardcoded the registered hmac. The device recognized the account being spent from. I can see that musig2_pubnonces was added to the PSBT.
I then ran it through walletprocesspsbt in wallet A. The resulting PSBT was longer, but it didn’t add its nonce to musig2_pubnonces.
Sjors
commented at 10:02 am on December 3, 2024:
member
Looking at the functional tests in 3649c2eb2053a0c166c68beb310c9c64ddc5b273 it seems the way this is designed to work is by swapping out the active tr([m/86'/1'/0']xpriv/<0;1>)/* descriptor for tr(musig([m/86'/1'/0']xpriv,other,other)/<0;1>)/*.
I guess that’s fine for the purpose of getting MuSig2 functionality in for experimental use, but it seems a bit unsafe and confusing for general use.
The test could make the intention a bit more clear by starting with blank wallets, only adding a tr() descriptor and mentioning in a comment that we just want its keys.
So instead of creating a watch-only wallet, I created a blank wallet. I imported the same tr(musig(A,L)/<0;1>/*) descriptor, but using the xpriv instead of xpub for A. It found the deposited coin. This time I started the withdrawal from the Core, so the Moosig could do its two calls to device in quick succession.
Even though I had “enable PSBT controls” selected, the GUI did not give me a chance to create a PSBT and immediately complained “Signing transaction failed” after I clicked “Send”.
The send RPC did work though it didn’t add a nonce. I had to use walletprocesspsbt for that.
I fed the result to Moosig generate_public_nonces and generate_partial_signatures. I fed the result to walletprocesspsbt et voila! Money back.
This is a metadata mirror of the GitHub repository
bitcoin/bitcoin.
This site is not affiliated with GitHub.
Content is generated from a GitHub metadata backup.
generated: 2024-12-03 15:12 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me