/Users/brunogarcia/projects/bitcoin-core-dev/src/musig.cpp
Line | Count | Source |
1 | | // Copyright (c) 2024-present The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #include <musig.h> |
6 | | #include <support/allocators/secure.h> |
7 | | |
8 | | #include <secp256k1_musig.h> |
9 | | |
10 | | static bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache) |
11 | 0 | { |
12 | | // Parse the pubkeys |
13 | 0 | std::vector<secp256k1_pubkey> secp_pubkeys; |
14 | 0 | std::vector<const secp256k1_pubkey*> pubkey_ptrs; |
15 | 0 | for (const CPubKey& pubkey : pubkeys) { |
16 | 0 | if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pubkeys.emplace_back(), pubkey.data(), pubkey.size())) { |
17 | 0 | return false; |
18 | 0 | } |
19 | 0 | } |
20 | 0 | pubkey_ptrs.reserve(secp_pubkeys.size()); |
21 | 0 | for (const secp256k1_pubkey& p : secp_pubkeys) { |
22 | 0 | pubkey_ptrs.push_back(&p); |
23 | 0 | } |
24 | | |
25 | | // Aggregate the pubkey |
26 | 0 | if (!secp256k1_musig_pubkey_agg(secp256k1_context_static, nullptr, &keyagg_cache, pubkey_ptrs.data(), pubkey_ptrs.size())) { |
27 | 0 | return false; |
28 | 0 | } |
29 | 0 | return true; |
30 | 0 | } |
31 | | |
32 | | static std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache) |
33 | 0 | { |
34 | | // Get the plain aggregated pubkey |
35 | 0 | secp256k1_pubkey agg_pubkey; |
36 | 0 | if (!secp256k1_musig_pubkey_get(secp256k1_context_static, &agg_pubkey, &keyagg_cache)) { |
37 | 0 | return std::nullopt; |
38 | 0 | } |
39 | | |
40 | | // Turn into CPubKey |
41 | 0 | unsigned char ser_agg_pubkey[CPubKey::COMPRESSED_SIZE]; |
42 | 0 | size_t ser_agg_pubkey_len = CPubKey::COMPRESSED_SIZE; |
43 | 0 | secp256k1_ec_pubkey_serialize(secp256k1_context_static, ser_agg_pubkey, &ser_agg_pubkey_len, &agg_pubkey, SECP256K1_EC_COMPRESSED); Line | Count | Source | 224 | 0 | #define SECP256K1_EC_COMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION) Line | Count | Source | 205 | 0 | #define SECP256K1_FLAGS_TYPE_COMPRESSION (1 << 1) |
| #define SECP256K1_EC_COMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION) Line | Count | Source | 210 | 0 | #define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8) |
|
|
44 | 0 | return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len); |
45 | 0 | } |
46 | | |
47 | | std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional<CPubKey>& expected_aggregate) |
48 | 0 | { |
49 | 0 | if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) { |
50 | 0 | return std::nullopt; |
51 | 0 | } |
52 | 0 | std::optional<CPubKey> agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache); |
53 | 0 | if (!agg_key.has_value()) return std::nullopt; |
54 | 0 | if (expected_aggregate.has_value() && expected_aggregate != agg_key) return std::nullopt; |
55 | 0 | return agg_key; |
56 | 0 | } |
57 | | |
58 | | std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys) |
59 | 0 | { |
60 | 0 | secp256k1_musig_keyagg_cache keyagg_cache; |
61 | 0 | return MuSig2AggregatePubkeys(pubkeys, keyagg_cache, std::nullopt); |
62 | 0 | } |
63 | | |
64 | | CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey) |
65 | 0 | { |
66 | 0 | CExtPubKey extpub; |
67 | 0 | extpub.nDepth = 0; |
68 | 0 | std::memset(extpub.vchFingerprint, 0, 4); |
69 | 0 | extpub.nChild = 0; |
70 | 0 | extpub.chaincode = MUSIG_CHAINCODE; |
71 | 0 | extpub.pubkey = pubkey; |
72 | 0 | return extpub; |
73 | 0 | } |
74 | | |
75 | | class MuSig2SecNonceImpl |
76 | | { |
77 | | private: |
78 | | //! The actual secnonce itself |
79 | | secure_unique_ptr<secp256k1_musig_secnonce> m_nonce; |
80 | | |
81 | | public: |
82 | 0 | MuSig2SecNonceImpl() : m_nonce{make_secure_unique<secp256k1_musig_secnonce>()} {} |
83 | | |
84 | | // Delete copy constructors |
85 | | MuSig2SecNonceImpl(const MuSig2SecNonceImpl&) = delete; |
86 | | MuSig2SecNonceImpl& operator=(const MuSig2SecNonceImpl&) = delete; |
87 | | |
88 | 0 | secp256k1_musig_secnonce* Get() const { return m_nonce.get(); } |
89 | 0 | void Invalidate() { m_nonce.reset(); } |
90 | 0 | bool IsValid() { return m_nonce != nullptr; } |
91 | | }; |
92 | | |
93 | 0 | MuSig2SecNonce::MuSig2SecNonce() : m_impl{std::make_unique<MuSig2SecNonceImpl>()} {} |
94 | | |
95 | 0 | MuSig2SecNonce::MuSig2SecNonce(MuSig2SecNonce&&) noexcept = default; |
96 | 0 | MuSig2SecNonce& MuSig2SecNonce::operator=(MuSig2SecNonce&&) noexcept = default; |
97 | | |
98 | 0 | MuSig2SecNonce::~MuSig2SecNonce() = default; |
99 | | |
100 | | secp256k1_musig_secnonce* MuSig2SecNonce::Get() const |
101 | 0 | { |
102 | 0 | return m_impl->Get(); |
103 | 0 | } |
104 | | |
105 | | void MuSig2SecNonce::Invalidate() |
106 | 0 | { |
107 | 0 | return m_impl->Invalidate(); |
108 | 0 | } |
109 | | |
110 | | bool MuSig2SecNonce::IsValid() |
111 | 0 | { |
112 | 0 | return m_impl->IsValid(); |
113 | 0 | } |
114 | | |
115 | | uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash) |
116 | 0 | { |
117 | 0 | HashWriter hasher; |
118 | 0 | hasher << script_pubkey << part_pubkey << sighash; |
119 | 0 | return hasher.GetSHA256(); |
120 | 0 | } |
121 | | |
122 | | std::optional<std::vector<uint8_t>> CreateMuSig2AggregateSig(const std::vector<CPubKey>& part_pubkeys, const CPubKey& aggregate_pubkey, const std::vector<std::pair<uint256, bool>>& tweaks, const uint256& sighash, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, const std::map<CPubKey, uint256>& partial_sigs) |
123 | 0 | { |
124 | 0 | if (!part_pubkeys.size()) return std::nullopt; |
125 | | |
126 | | // Get the keyagg cache and aggregate pubkey |
127 | 0 | secp256k1_musig_keyagg_cache keyagg_cache; |
128 | 0 | if (!MuSig2AggregatePubkeys(part_pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt; |
129 | | |
130 | | // Check if enough pubnonces and partial sigs |
131 | 0 | if (pubnonces.size() != part_pubkeys.size()) return std::nullopt; |
132 | 0 | if (partial_sigs.size() != part_pubkeys.size()) return std::nullopt; |
133 | | |
134 | | // Parse the pubnonces and partial sigs |
135 | 0 | std::vector<std::tuple<secp256k1_pubkey, secp256k1_musig_pubnonce, secp256k1_musig_partial_sig>> signers_data; |
136 | 0 | std::vector<const secp256k1_musig_pubnonce*> pubnonce_ptrs; |
137 | 0 | std::vector<const secp256k1_musig_partial_sig*> partial_sig_ptrs; |
138 | 0 | for (const CPubKey& part_pk : part_pubkeys) { |
139 | 0 | const auto& pn_it = pubnonces.find(part_pk); |
140 | 0 | if (pn_it == pubnonces.end()) return std::nullopt; |
141 | 0 | const std::vector<uint8_t> pubnonce = pn_it->second; |
142 | 0 | if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) return std::nullopt; |
143 | 0 | const auto& it = partial_sigs.find(part_pk); |
144 | 0 | if (it == partial_sigs.end()) return std::nullopt; |
145 | 0 | const uint256& partial_sig = it->second; |
146 | |
|
147 | 0 | auto& [secp_pk, secp_pn, secp_ps] = signers_data.emplace_back(); |
148 | |
|
149 | 0 | if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pk, part_pk.data(), part_pk.size())) { |
150 | 0 | return std::nullopt; |
151 | 0 | } |
152 | | |
153 | 0 | if (!secp256k1_musig_pubnonce_parse(secp256k1_context_static, &secp_pn, pubnonce.data())) { |
154 | 0 | return std::nullopt; |
155 | 0 | } |
156 | | |
157 | 0 | if (!secp256k1_musig_partial_sig_parse(secp256k1_context_static, &secp_ps, partial_sig.data())) { |
158 | 0 | return std::nullopt; |
159 | 0 | } |
160 | 0 | } |
161 | 0 | pubnonce_ptrs.reserve(signers_data.size()); |
162 | 0 | partial_sig_ptrs.reserve(signers_data.size()); |
163 | 0 | for (auto& [_, pn, ps] : signers_data) { |
164 | 0 | pubnonce_ptrs.push_back(&pn); |
165 | 0 | partial_sig_ptrs.push_back(&ps); |
166 | 0 | } |
167 | | |
168 | | // Aggregate nonces |
169 | 0 | secp256k1_musig_aggnonce aggnonce; |
170 | 0 | if (!secp256k1_musig_nonce_agg(secp256k1_context_static, &aggnonce, pubnonce_ptrs.data(), pubnonce_ptrs.size())) { |
171 | 0 | return std::nullopt; |
172 | 0 | } |
173 | | |
174 | | // Apply tweaks |
175 | 0 | for (const auto& [tweak, xonly] : tweaks) { |
176 | 0 | if (xonly) { |
177 | 0 | if (!secp256k1_musig_pubkey_xonly_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) { |
178 | 0 | return std::nullopt; |
179 | 0 | } |
180 | 0 | } else if (!secp256k1_musig_pubkey_ec_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) { |
181 | 0 | return std::nullopt; |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | // Create musig_session |
186 | 0 | secp256k1_musig_session session; |
187 | 0 | if (!secp256k1_musig_nonce_process(secp256k1_context_static, &session, &aggnonce, sighash.data(), &keyagg_cache)) { |
188 | 0 | return std::nullopt; |
189 | 0 | } |
190 | | |
191 | | // Verify partial sigs |
192 | 0 | for (const auto& [pk, pb, ps] : signers_data) { |
193 | 0 | if (!secp256k1_musig_partial_sig_verify(secp256k1_context_static, &ps, &pb, &pk, &keyagg_cache, &session)) { |
194 | 0 | return std::nullopt; |
195 | 0 | } |
196 | 0 | } |
197 | | |
198 | | // Aggregate partial sigs |
199 | 0 | std::vector<uint8_t> sig; |
200 | 0 | sig.resize(64); |
201 | 0 | if (!secp256k1_musig_partial_sig_agg(secp256k1_context_static, sig.data(), &session, partial_sig_ptrs.data(), partial_sig_ptrs.size())) { |
202 | 0 | return std::nullopt; |
203 | 0 | } |
204 | | |
205 | 0 | return sig; |
206 | 0 | } |