diff --git a/doc/descriptors.md b/doc/descriptors.md index 928e8806c6a..292773fccf9 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -69,6 +69,7 @@ Output descriptors currently support: - `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,sortedmulti_a(2,2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc))` describes a P2TR output with the `c6...` x-only pubkey as internal key, and a single `multi_a` script that needs 2 signatures with 2 specified x-only keys, which will be sorted lexicographically. - `wsh(sortedmulti(2,[6f53d49c/44h/1h/0h]tpubDDjsCRDQ9YzyaAq9rspCfq8RZFrWoBpYnLxK6sS2hS2yukqSczgcYiur8Scx4Hd5AZatxTuzMtJQJhchufv1FRFanLqUP7JHwusSSpfcEp2/0/*,[e6807791/44h/1h/0h]tpubDDAfvogaaAxaFJ6c15ht7Tq6ZmiqFYfrSmZsHu7tHXBgnjMZSHAeHSwhvjARNA6Qybon4ksPksjRbPDVp7yXA1KjTjSd5x18KHqbppnXP1s/0/*,[367c9cfa/44h/1h/0h]tpubDDtPnSgWYk8dDnaDwnof4ehcnjuL5VoUt1eW2MoAed1grPHuXPDnkX1fWMvXfcz3NqFxPbhqNZ3QBdYjLz2hABeM9Z2oqMR1Gt2HHYDoCgh/0/*))#av0kxgw0` describes a *2-of-3* multisig. For brevity, the internal "change" descriptor accompanying the above external "receiving" descriptor is not included here, but it typically differs only in the xpub derivation steps, ending in `/1/*` for change addresses. - `wsh(thresh(4,pk([7258e4f9/44h/1h/0h]tpubDCZrkQoEU3845aFKUu9VQBYWZtrTwxMzcxnBwKFCYXHD6gEXvtFcxddCCLFsEwmxQaG15izcHxj48SXg1QS5FQGMBx5Ak6deXKPAL7wauBU/0/*),s:pk([c80b1469/44h/1h/0h]tpubDD3UwwHoNUF4F3Vi5PiUVTc3ji1uThuRfFyBexTSHoAcHuWW2z8qEE2YujegcLtgthr3wMp3ZauvNG9eT9xfJyxXCfNty8h6rDBYU8UU1qq/0/*),s:pk([4e5024fe/44h/1h/0h]tpubDDLrpPymPLSCJyCMLQdmcWxrAWwsqqssm5NdxT2WSdEBPSXNXxwbeKtsHAyXPpLkhUyKovtZgCi47QxVpw9iVkg95UUgeevyAqtJ9dqBqa1/0/*),s:pk([3b1d1ee9/44h/1h/0h]tpubDCmDTANBWPzf6d8Ap1J5Ku7J1Ay92MpHMrEV7M5muWxCrTBN1g5f1NPcjMEL6dJHxbvEKNZtYCdowaSTN81DAyLsmv6w6xjJHCQNkxrsrfu/0/*),sln:after(840000),sln:after(1050000),sln:after(1260000)))#k28080kv` describes a Miniscript multisig with spending policy: `thresh(4,pk(key_1),pk(key_2),pk(key_3),pk(key_4),after(t1),after(t2),after(t3))` that starts as 4-of-4 and "decays" to 3-of-4, 2-of-4, and finally 1-of-4 at each future halvening block height. For brevity, the internal "change" descriptor accompanying the above external "receiving" descriptor is not included here, but it typically differs only in the xpub derivation steps, ending in `/1/*` for change addresses. +- `tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)` describes a MuSig2 multisig with key derivation. The internal keys are derived at `m/0/*` from the aggregate key computed from the 2 participants. ## Reference diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8be486579c2..7f02b9258c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,7 +31,7 @@ message("Configuring secp256k1 subtree...") set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_ECDH OFF CACHE BOOL "" FORCE) set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE) -set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE BOOL "" FORCE) +set(SECP256K1_ENABLE_MODULE_MUSIG ON CACHE BOOL "" FORCE) set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) set(SECP256K1_BUILD_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) set(SECP256K1_BUILD_EXHAUSTIVE_TESTS ${BUILD_TESTS} CACHE BOOL "" FORCE) @@ -161,6 +161,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL key.cpp key_io.cpp merkleblock.cpp + musig.cpp net_permissions.cpp net_types.cpp netaddress.cpp diff --git a/src/musig.cpp b/src/musig.cpp new file mode 100644 index 00000000000..b3329543127 --- /dev/null +++ b/src/musig.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include + +bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache) +{ + // Parse the pubkeys + std::vector secp_pubkeys; + std::vector pubkey_ptrs; + for (const CPubKey& pubkey : pubkeys) { + if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pubkeys.emplace_back(), pubkey.data(), pubkey.size())) { + return false; + } + } + pubkey_ptrs.reserve(secp_pubkeys.size()); + for (const secp256k1_pubkey& p : secp_pubkeys) { + pubkey_ptrs.push_back(&p); + } + + // Aggregate the pubkey + if (!secp256k1_musig_pubkey_agg(secp256k1_context_static, nullptr, &keyagg_cache, pubkey_ptrs.data(), pubkey_ptrs.size())) { + return false; + } + return true; +} + +std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache) +{ + // Get the plain aggregated pubkey + secp256k1_pubkey agg_pubkey; + if (!secp256k1_musig_pubkey_get(secp256k1_context_static, &agg_pubkey, &keyagg_cache)) { + return std::nullopt; + } + + // Turn into CPubKey + unsigned char ser_agg_pubkey[CPubKey::COMPRESSED_SIZE]; + size_t ser_agg_pubkey_len = CPubKey::COMPRESSED_SIZE; + secp256k1_ec_pubkey_serialize(secp256k1_context_static, ser_agg_pubkey, &ser_agg_pubkey_len, &agg_pubkey, SECP256K1_EC_COMPRESSED); + return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len); +} + +std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys) +{ + secp256k1_musig_keyagg_cache keyagg_cache; + if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) { + return std::nullopt; + } + return GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); +} diff --git a/src/musig.h b/src/musig.h new file mode 100644 index 00000000000..d46a67f65ec --- /dev/null +++ b/src/musig.h @@ -0,0 +1,26 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_MUSIG_H +#define BITCOIN_MUSIG_H + +#include + +#include +#include + +struct secp256k1_musig_keyagg_cache; + +//! MuSig2 chaincode as defined by BIP 328 +using namespace util::hex_literals; +constexpr uint256 MUSIG_CHAINCODE{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8}; + +//! Create a secp256k1_musig_keyagg_cache from the pubkeys in their current order. This is necessary for most MuSig2 operations +bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache); +//! Retrieve the full aggregate pubkey from the secp256k1_musig_keyagg_cache +std::optional GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& cache); +//! Compute the full aggregate pubkey from the given participant pubkeys in their current order +std::optional MuSig2AggregatePubkeys(const std::vector& pubkeys); + +#endif // BITCOIN_MUSIG_H diff --git a/src/pubkey.cpp b/src/pubkey.cpp index a4ca9a170a9..6041c89e7f1 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -197,20 +197,26 @@ constexpr XOnlyPubKey XOnlyPubKey::NUMS_H{ []() consteval { return XOnlyPubKey{"50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"_hex_u8}; }(), }; -std::vector XOnlyPubKey::GetKeyIDs() const +std::vector XOnlyPubKey::GetCPubKeys() const { - std::vector out; - // For now, use the old full pubkey-based key derivation logic. As it is indexed by - // Hash160(full pubkey), we need to return both a version prefixed with 0x02, and one - // with 0x03. + std::vector out; unsigned char b[33] = {0x02}; std::copy(m_keydata.begin(), m_keydata.end(), b + 1); CPubKey fullpubkey; fullpubkey.Set(b, b + 33); - out.push_back(fullpubkey.GetID()); + out.push_back(fullpubkey); b[0] = 0x03; fullpubkey.Set(b, b + 33); - out.push_back(fullpubkey.GetID()); + out.push_back(fullpubkey); + return out; +} + +std::vector XOnlyPubKey::GetKeyIDs() const +{ + std::vector out; + for (const CPubKey& pk : GetCPubKeys()) { + out.push_back(pk.GetID()); + } return out; } diff --git a/src/pubkey.h b/src/pubkey.h index cbc827dc606..442dc2d6431 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -283,9 +283,13 @@ public: std::optional> CreateTapTweak(const uint256* merkle_root) const; /** Returns a list of CKeyIDs for the CPubKeys that could have been used to create this XOnlyPubKey. + * As the CKeyID is the Hash160(full pubkey), the produced CKeyIDs are for the versions of this + * XOnlyPubKey with 0x02 and 0x03 prefixes. * This is needed for key lookups since keys are indexed by CKeyID. */ std::vector GetKeyIDs() const; + /** Returns this XOnlyPubKey with 0x02 and 0x03 prefixes */ + std::vector GetCPubKeys() const; CPubKey GetEvenCorrespondingCPubKey() const; diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 71645c87462..bd819d365ae 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include