Line data Source code
1 : // Copyright (c) 2018-2025 The Dash 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 : #ifndef DASH_CRYPTO_BLS_H
6 : #define DASH_CRYPTO_BLS_H
7 :
8 : #include <hash.h>
9 : #include <serialize.h>
10 : #include <uint256.h>
11 : #include <util/strencodings.h>
12 :
13 : // bls-dash uses relic, which may define DEBUG and ERROR, which leads to many warnings in some build setups
14 : #undef ERROR
15 : #undef DEBUG
16 : #include <dashbls/bls.hpp>
17 : #include <dashbls/privatekey.hpp>
18 : #include <dashbls/elements.hpp>
19 : #include <dashbls/schemes.hpp>
20 : #include <dashbls/threshold.hpp>
21 : #undef DOUBLE
22 : #undef SEED
23 :
24 : #include <array>
25 : #include <atomic>
26 : #include <mutex>
27 : #include <ranges>
28 :
29 : namespace bls {
30 : extern std::atomic<bool> bls_legacy_scheme;
31 : }
32 :
33 : // reversed BLS12-381
34 : constexpr int BLS_CURVE_ID_SIZE{32};
35 : constexpr int BLS_CURVE_SECKEY_SIZE{32};
36 : constexpr int BLS_CURVE_PUBKEY_SIZE{48};
37 : constexpr int BLS_CURVE_SIG_SIZE{96};
38 :
39 : class CBLSSignature;
40 : class CBLSPublicKey;
41 :
42 : template <typename ImplType, size_t _SerSize, typename C>
43 : class CBLSWrapper
44 : {
45 : friend class CBLSSecretKey;
46 : friend class CBLSPublicKey;
47 : friend class CBLSSignature;
48 :
49 : protected:
50 : ImplType impl;
51 13987451 : bool fValid{false};
52 : mutable uint256 cachedHash;
53 :
54 : public:
55 : static constexpr size_t SerSize = _SerSize;
56 :
57 24861134 : explicit CBLSWrapper() = default;
58 :
59 5058764 : CBLSWrapper(const CBLSWrapper& ref) = default;
60 4064578 : CBLSWrapper& operator=(const CBLSWrapper& ref) = default;
61 3113768 : CBLSWrapper(CBLSWrapper&& ref) noexcept
62 1556884 : {
63 1556882 : std::swap(impl, ref.impl);
64 1556882 : std::swap(fValid, ref.fValid);
65 1556882 : std::swap(cachedHash, ref.cachedHash);
66 1556882 : }
67 2275872 : CBLSWrapper& operator=(CBLSWrapper&& ref) noexcept
68 : {
69 2275872 : std::swap(impl, ref.impl);
70 2275872 : std::swap(fValid, ref.fValid);
71 2275872 : std::swap(cachedHash, ref.cachedHash);
72 2275872 : return *this;
73 : }
74 :
75 18272437 : virtual ~CBLSWrapper() = default;
76 :
77 75677 : bool operator==(const C& r) const
78 : {
79 75677 : return fValid == r.fValid && impl == r.impl;
80 : }
81 16613 : bool operator!=(const C& r) const
82 : {
83 16613 : return !((*this) == r);
84 : }
85 15 : bool operator<(const C& r) const
86 : {
87 15 : return GetHash() < r.GetHash();
88 : }
89 :
90 6724000 : bool IsValid() const
91 : {
92 6724000 : return fValid;
93 : }
94 :
95 4718807 : void Reset()
96 : {
97 4718807 : *(static_cast<C*>(this)) = C();
98 4718807 : }
99 :
100 3829187 : void SetBytes(Span<const uint8_t> vecBytes, const bool specificLegacyScheme)
101 : {
102 3829187 : if (vecBytes.size() != SerSize) {
103 0 : Reset();
104 0 : return;
105 : }
106 :
107 283233347 : if (std::ranges::all_of(vecBytes, [](uint8_t c) { return c == 0; })) {
108 3433638 : Reset();
109 3433638 : } else {
110 : try {
111 395549 : impl = ImplType::FromBytes(bls::Bytes(vecBytes.data(), vecBytes.size()), specificLegacyScheme);
112 395543 : if (impl == ImplType()) {
113 0 : Reset();
114 0 : cachedHash.SetNull();
115 0 : return;
116 : }
117 395542 : fValid = true;
118 395547 : } catch (...) {
119 5 : Reset();
120 5 : }
121 : }
122 3829185 : cachedHash.SetNull();
123 3829196 : }
124 :
125 19044 : std::vector<uint8_t> ToByteVector(const bool specificLegacyScheme) const
126 : {
127 19044 : if (!fValid) {
128 0 : return std::vector<uint8_t>(SerSize, 0);
129 : }
130 19044 : return impl.Serialize(specificLegacyScheme);
131 19044 : }
132 :
133 4903282 : std::array<uint8_t, SerSize> ToBytes(const bool specificLegacyScheme) const
134 : {
135 4903282 : if (!fValid) {
136 3884568 : return std::array<uint8_t, SerSize>{};
137 : }
138 1018714 : return impl.SerializeToArray(specificLegacyScheme);
139 4903282 : }
140 :
141 105789 : const uint256& GetHash() const
142 : {
143 105789 : if (cachedHash.IsNull()) {
144 61378 : cachedHash = ::SerializeHash(*this);
145 61378 : }
146 105789 : return cachedHash;
147 : }
148 :
149 1138 : bool SetHexStr(const std::string& str, const bool specificLegacyScheme)
150 : {
151 1138 : if (!IsHex(str)) {
152 2 : Reset();
153 2 : return false;
154 : }
155 1136 : auto b = ParseHex(str);
156 1136 : if (b.size() != SerSize) {
157 6 : Reset();
158 6 : return false;
159 : }
160 1130 : SetBytes(b, specificLegacyScheme);
161 1130 : return IsValid();
162 1138 : }
163 :
164 : inline void Serialize(CSizeComputer& s) const
165 : {
166 : s.seek(SerSize);
167 : }
168 :
169 : template <typename Stream>
170 741725 : inline void Serialize(Stream& s, const bool specificLegacyScheme) const
171 : {
172 741725 : const auto bytes{ToBytes(specificLegacyScheme)};
173 741725 : s.write(AsBytes(Span{bytes.data(), SerSize}));
174 741725 : }
175 :
176 : template <typename Stream>
177 427875 : inline void Serialize(Stream& s) const
178 : {
179 427875 : Serialize(s, bls::bls_legacy_scheme.load());
180 427875 : }
181 :
182 : template <typename Stream>
183 3763902 : inline void Unserialize(Stream& s, const bool specificLegacyScheme)
184 : {
185 3763902 : std::array<uint8_t, SerSize> vecBytes{};
186 3763902 : s.read(AsWritableBytes(Span{vecBytes.data(), SerSize}));
187 3763902 : SetBytes(vecBytes, specificLegacyScheme);
188 :
189 3763902 : if (!CheckMalleable(vecBytes, specificLegacyScheme)) {
190 : // If CheckMalleable failed with specificLegacyScheme, we need to try again with the opposite scheme.
191 : // Probably we received the BLS object sent with legacy scheme, but in the meanwhile the fork activated.
192 0 : SetBytes(vecBytes, !specificLegacyScheme);
193 0 : if (!CheckMalleable(vecBytes, !specificLegacyScheme)) {
194 : // Both attempts failed
195 0 : throw std::ios_base::failure("malleable BLS object");
196 : } else {
197 : // Indeed the received vecBytes was in opposite scheme. But we can't keep it (mixing with the new scheme will lead to undefined behavior)
198 : // Therefore, resetting current object (basically marking it as invalid).
199 0 : Reset();
200 : }
201 0 : }
202 3763902 : }
203 :
204 : template <typename Stream>
205 478006 : inline void Unserialize(Stream& s)
206 : {
207 478006 : Unserialize(s, bls::bls_legacy_scheme.load());
208 478006 : }
209 :
210 3826044 : inline bool CheckMalleable(Span<uint8_t> vecBytes, const bool specificLegacyScheme) const
211 : {
212 3826044 : const auto bytes{ToBytes(specificLegacyScheme)};
213 3826044 : if (memcmp(vecBytes.data(), bytes.data(), SerSize)) {
214 : // TODO not sure if this is actually possible with the BLS libs. I'm assuming here that somewhere deep inside
215 : // these libs masking might happen, so that 2 different binary representations could result in the same object
216 : // representation
217 0 : return false;
218 : }
219 3826044 : return true;
220 3826044 : }
221 :
222 302956 : inline std::string ToString(const bool specificLegacyScheme) const
223 : {
224 302956 : auto buf = ToBytes(specificLegacyScheme);
225 302956 : return HexStr(buf);
226 : }
227 :
228 255758 : inline std::string ToString() const
229 : {
230 255758 : return ToString(bls::bls_legacy_scheme.load());
231 : }
232 : };
233 :
234 : struct CBLSIdImplicit : public uint256
235 : {
236 242856 : CBLSIdImplicit() = default;
237 160113 : CBLSIdImplicit(const uint256& id)
238 80056 : {
239 80057 : memcpy(begin(), id.begin(), sizeof(uint256));
240 160113 : }
241 : static CBLSIdImplicit FromBytes(const uint8_t* buffer, const bool fLegacy)
242 : {
243 : CBLSIdImplicit instance;
244 : memcpy(instance.begin(), buffer, sizeof(CBLSIdImplicit));
245 : return instance;
246 : }
247 : [[nodiscard]] std::vector<uint8_t> Serialize(const bool fLegacy) const
248 : {
249 : return {begin(), end()};
250 : }
251 7345 : [[nodiscard]] std::array<uint8_t, 32> SerializeToArray(const bool fLegacy) const { return m_data; }
252 : };
253 :
254 : class CBLSId : public CBLSWrapper<CBLSIdImplicit, BLS_CURVE_ID_SIZE, CBLSId>
255 : {
256 : public:
257 : using CBLSWrapper::operator=;
258 : using CBLSWrapper::operator==;
259 : using CBLSWrapper::operator!=;
260 : using CBLSWrapper::CBLSWrapper;
261 :
262 63911 : CBLSId() = default;
263 : explicit CBLSId(const uint256& nHash);
264 : };
265 :
266 : //! CBLSSecretKey is invariant to BLS scheme for Creation / Serialization / Deserialization
267 : class CBLSSecretKey : public CBLSWrapper<bls::PrivateKey, BLS_CURVE_SECKEY_SIZE, CBLSSecretKey>
268 : {
269 : public:
270 : using CBLSWrapper::operator=;
271 : using CBLSWrapper::operator==;
272 : using CBLSWrapper::operator!=;
273 : using CBLSWrapper::CBLSWrapper;
274 :
275 153637 : CBLSSecretKey() = default;
276 1350 : explicit CBLSSecretKey(Span<const unsigned char> vecBytes)
277 1350 : {
278 : // The second param here is not 'is_legacy', but `modOrder`
279 675 : SetBytes(vecBytes, false);
280 1350 : }
281 557819 : CBLSSecretKey(const CBLSSecretKey&) = default;
282 13308 : CBLSSecretKey& operator=(const CBLSSecretKey&) = default;
283 :
284 : void AggregateInsecure(const CBLSSecretKey& o);
285 : static CBLSSecretKey AggregateInsecure(Span<CBLSSecretKey> sks);
286 :
287 : #ifndef BUILD_BITCOIN_INTERNAL
288 : //! MakeNewKey() is invariant to BLS scheme
289 : void MakeNewKey();
290 : #endif
291 : //! SecretKeyShare() is invariant to BLS scheme
292 : bool SecretKeyShare(Span<CBLSSecretKey> msk, const CBLSId& id);
293 :
294 : //! GetPublicKey() is invariant to BLS scheme
295 : [[nodiscard]] CBLSPublicKey GetPublicKey() const;
296 : [[nodiscard]] CBLSSignature Sign(const uint256& hash, const bool specificLegacyScheme) const;
297 : };
298 :
299 : class CBLSPublicKey : public CBLSWrapper<bls::G1Element, BLS_CURVE_PUBKEY_SIZE, CBLSPublicKey>
300 : {
301 : friend class CBLSSecretKey;
302 : friend class CBLSSignature;
303 :
304 : public:
305 : using CBLSWrapper::operator=;
306 : using CBLSWrapper::operator==;
307 : using CBLSWrapper::operator!=;
308 : using CBLSWrapper::CBLSWrapper;
309 :
310 12060908 : CBLSPublicKey() = default;
311 :
312 : void AggregateInsecure(const CBLSPublicKey& o);
313 : static CBLSPublicKey AggregateInsecure(Span<CBLSPublicKey> pks);
314 :
315 : bool PublicKeyShare(Span<CBLSPublicKey> mpk, const CBLSId& id);
316 : bool DHKeyExchange(const CBLSSecretKey& sk, const CBLSPublicKey& pk);
317 :
318 : };
319 :
320 : class CBLSPublicKeyVersionWrapper {
321 : private:
322 : CBLSPublicKey& obj;
323 : bool legacy;
324 : public:
325 2456910 : CBLSPublicKeyVersionWrapper(CBLSPublicKey& obj, bool legacy)
326 1228455 : : obj(obj)
327 1228455 : , legacy(legacy)
328 2456910 : {}
329 : template <typename Stream>
330 123176 : inline void Serialize(Stream& s) const {
331 123176 : obj.Serialize(s, legacy);
332 123176 : }
333 : template <typename Stream>
334 1105278 : inline void Unserialize(Stream& s) {
335 1105278 : obj.Unserialize(s, legacy);
336 1105278 : }
337 : };
338 :
339 : class CBLSSignature : public CBLSWrapper<bls::G2Element, BLS_CURVE_SIG_SIZE, CBLSSignature>
340 : {
341 : friend class CBLSSecretKey;
342 :
343 : public:
344 : using CBLSWrapper::operator==;
345 : using CBLSWrapper::operator!=;
346 : using CBLSWrapper::CBLSWrapper;
347 :
348 12421239 : CBLSSignature() = default;
349 2 : explicit CBLSSignature(Span<const unsigned char> bytes, bool is_serialized_legacy)
350 2 : {
351 1 : SetBytes(bytes, is_serialized_legacy);
352 2 : }
353 8638931 : CBLSSignature(const CBLSSignature&) = default;
354 3108231 : CBLSSignature& operator=(const CBLSSignature&) = default;
355 :
356 : void AggregateInsecure(const CBLSSignature& o);
357 : static CBLSSignature AggregateInsecure(Span<CBLSSignature> sigs);
358 : static CBLSSignature AggregateSecure(Span<CBLSSignature> sigs, Span<CBLSPublicKey> pks, const uint256& hash);
359 :
360 : void SubInsecure(const CBLSSignature& o);
361 : [[nodiscard]] bool VerifyInsecure(const CBLSPublicKey& pubKey, const uint256& hash, const bool specificLegacyScheme) const;
362 : [[nodiscard]] bool VerifyInsecure(const CBLSPublicKey& pubKey, const uint256& hash) const;
363 : [[nodiscard]] bool VerifyInsecureAggregated(Span<CBLSPublicKey> pubKeys, Span<uint256> hashes) const;
364 :
365 : [[nodiscard]] bool VerifySecureAggregated(Span<CBLSPublicKey> pks, const uint256& hash) const;
366 :
367 : bool Recover(Span<CBLSSignature> sigs, Span<CBLSId> ids);
368 : };
369 :
370 : class CBLSSignatureVersionWrapper {
371 : private:
372 : CBLSSignature& obj;
373 : bool legacy;
374 : public:
375 4742574 : CBLSSignatureVersionWrapper(CBLSSignature& obj, bool legacy)
376 2371287 : : obj(obj)
377 2371287 : , legacy(legacy)
378 4742574 : {}
379 : template <typename Stream>
380 190674 : inline void Serialize(Stream& s) const {
381 190674 : obj.Serialize(s, legacy);
382 190674 : }
383 : template <typename Stream>
384 2180613 : inline void Unserialize(Stream& s) {
385 2180613 : obj.Unserialize(s, legacy);
386 2180613 : }
387 : };
388 :
389 : #ifndef BUILD_BITCOIN_INTERNAL
390 : template<typename BLSObject>
391 : class CBLSLazyWrapper
392 : {
393 : private:
394 : mutable std::mutex mutex;
395 :
396 2674849 : mutable std::array<uint8_t, BLSObject::SerSize> vecBytes{};
397 :
398 : mutable BLSObject obj;
399 2674849 : mutable bool objInitialized{false};
400 :
401 : // Indicates if the value contained in vecBytes is valid
402 2674849 : mutable bool bufValid{false};
403 2196724 : mutable bool bufLegacyScheme{true};
404 :
405 : mutable uint256 hash;
406 :
407 : public:
408 1912503 : CBLSLazyWrapper() :
409 478125 : bufLegacyScheme(bls::bls_legacy_scheme.load())
410 1434378 : {}
411 :
412 8786898 : explicit CBLSLazyWrapper(const CBLSLazyWrapper& r)
413 4393450 : {
414 2196719 : *this = r;
415 4393450 : }
416 5349517 : virtual ~CBLSLazyWrapper() = default;
417 :
418 2205128 : CBLSLazyWrapper& operator=(const CBLSLazyWrapper& r)
419 : {
420 2205128 : std::unique_lock<std::mutex> l(r.mutex);
421 2205128 : bufValid = r.bufValid;
422 2205128 : bufLegacyScheme = r.bufLegacyScheme;
423 2205128 : if (r.bufValid) {
424 1387039 : vecBytes = r.vecBytes;
425 1387039 : } else {
426 818089 : std::fill(vecBytes.begin(), vecBytes.end(), 0);
427 : }
428 2205085 : objInitialized = r.objInitialized;
429 2205085 : if (r.objInitialized) {
430 939590 : obj = r.obj;
431 939590 : } else {
432 1265495 : obj.Reset();
433 : }
434 2205042 : hash = r.hash;
435 : return *this;
436 2205128 : }
437 :
438 : inline void Serialize(CSizeComputer& s) const
439 : {
440 : s.seek(BLSObject::SerSize);
441 : }
442 :
443 : template<typename Stream>
444 278258 : inline void Serialize(Stream& s, const bool specificLegacyScheme) const
445 : {
446 278258 : std::unique_lock<std::mutex> l(mutex);
447 278258 : if (!objInitialized && !bufValid) {
448 907 : std::fill(vecBytes.begin(), vecBytes.end(), 0);
449 278258 : } else if (!bufValid || (bufLegacyScheme != specificLegacyScheme)) {
450 32570 : vecBytes = obj.ToBytes(specificLegacyScheme);
451 32570 : bufValid = true;
452 32570 : bufLegacyScheme = specificLegacyScheme;
453 32570 : hash.SetNull();
454 32570 : }
455 278258 : s.write(MakeByteSpan(vecBytes));
456 278258 : }
457 :
458 : template<typename Stream>
459 222315 : inline void Serialize(Stream& s) const
460 : {
461 222315 : Serialize(s, bufLegacyScheme);
462 222315 : }
463 :
464 : template<typename Stream>
465 92239 : inline void Unserialize(Stream& s, const bool specificLegacyScheme) const
466 : {
467 92239 : std::unique_lock<std::mutex> l(mutex);
468 92239 : s.read(AsWritableBytes(Span{vecBytes.data(), BLSObject::SerSize}));
469 186371 : bufValid = std::any_of(vecBytes.begin(), vecBytes.end(), [](uint8_t c) { return c != 0; });
470 92239 : bufLegacyScheme = specificLegacyScheme;
471 92239 : objInitialized = false;
472 92239 : hash.SetNull();
473 92239 : }
474 :
475 : template<typename Stream>
476 66384 : inline void Unserialize(Stream& s) const
477 : {
478 66384 : Unserialize(s, bufLegacyScheme);
479 66384 : }
480 :
481 27757 : void Set(const BLSObject& _obj, const bool specificLegacyScheme)
482 : {
483 27757 : std::unique_lock<std::mutex> l(mutex);
484 27757 : bufValid = false;
485 27757 : bufLegacyScheme = specificLegacyScheme;
486 27757 : objInitialized = true;
487 27757 : obj = _obj;
488 27757 : hash.SetNull();
489 27757 : }
490 271509 : const BLSObject& Get() const
491 : {
492 271509 : std::unique_lock<std::mutex> l(mutex);
493 271509 : static BLSObject invalidObj;
494 271509 : if (!bufValid && !objInitialized) {
495 2398 : return invalidObj;
496 : }
497 269111 : if (!objInitialized) {
498 62161 : obj.SetBytes(vecBytes, bufLegacyScheme);
499 62159 : if (!obj.IsValid()) {
500 4 : bufValid = false;
501 4 : return invalidObj;
502 : }
503 62154 : if (!obj.CheckMalleable(vecBytes, bufLegacyScheme)) {
504 0 : bufValid = false;
505 0 : return invalidObj;
506 : }
507 62155 : objInitialized = true;
508 62155 : }
509 269105 : return obj;
510 271511 : }
511 :
512 1526972 : bool operator==(const CBLSLazyWrapper& r) const
513 : {
514 1526972 : if (&r == this) return true;
515 : {
516 1472395 : std::scoped_lock lock(mutex, r.mutex);
517 : // If neither bufValid or objInitialized are set, then the object is the default object.
518 1472395 : const bool is_default{!bufValid && !objInitialized};
519 1472395 : const bool r_is_default{!r.bufValid && !r.objInitialized};
520 : // If both are default; they are equal.
521 1472395 : if (is_default && r_is_default) return true;
522 : // If one is default and the other isn't, we are not equal
523 1468434 : if (is_default != r_is_default) return false;
524 :
525 1313664 : if (bufValid && r.bufValid && bufLegacyScheme == r.bufLegacyScheme) {
526 1313660 : return vecBytes == r.vecBytes;
527 : }
528 4 : if (objInitialized && r.objInitialized) {
529 4 : return obj == r.obj;
530 : }
531 1472395 : }
532 0 : return Get() == r.Get();
533 1526972 : }
534 :
535 358819 : bool operator!=(const CBLSLazyWrapper& r) const
536 : {
537 358819 : return !(*this == r);
538 : }
539 :
540 8311 : uint256 GetHash() const
541 : {
542 8311 : std::unique_lock<std::mutex> l(mutex);
543 8311 : if (!objInitialized && !bufValid) {
544 11 : std::fill(vecBytes.begin(), vecBytes.end(), 0);
545 11 : hash.SetNull();
546 8311 : } else if (!bufValid) {
547 2 : vecBytes = obj.ToBytes(bufLegacyScheme);
548 2 : bufValid = true;
549 2 : hash.SetNull();
550 2 : }
551 8311 : if (hash.IsNull()) {
552 7064 : CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
553 7064 : ss.write(MakeByteSpan(vecBytes));
554 7064 : hash = ss.GetHash();
555 7064 : }
556 8311 : return hash;
557 8311 : }
558 :
559 4589 : bool IsLegacy() const
560 : {
561 4589 : return bufLegacyScheme;
562 : }
563 :
564 1 : void SetLegacy(bool specificLegacyScheme)
565 : {
566 1 : bufLegacyScheme = specificLegacyScheme;
567 1 : }
568 :
569 26426 : std::string ToString() const
570 : {
571 26426 : return Get().ToString(bufLegacyScheme);
572 : }
573 : };
574 : using CBLSLazySignature = CBLSLazyWrapper<CBLSSignature>;
575 : using CBLSLazyPublicKey = CBLSLazyWrapper<CBLSPublicKey>;
576 :
577 : class CBLSLazyPublicKeyVersionWrapper {
578 : private:
579 : CBLSLazyPublicKey& obj;
580 : bool legacy;
581 : public:
582 163592 : CBLSLazyPublicKeyVersionWrapper(CBLSLazyPublicKey& obj, bool legacy)
583 81796 : : obj(obj)
584 81796 : , legacy(legacy)
585 163592 : {}
586 : template <typename Stream>
587 55942 : inline void Serialize(Stream& s) const {
588 55942 : obj.Serialize(s, legacy);
589 55942 : }
590 : template <typename Stream>
591 25854 : inline void Unserialize(Stream& s) {
592 25854 : obj.Unserialize(s, legacy);
593 25854 : }
594 : };
595 : #endif
596 :
597 : using BLSVerificationVectorPtr = std::shared_ptr<std::vector<CBLSPublicKey>>;
598 :
599 : bool BLSInit();
600 :
601 : #endif // DASH_CRYPTO_BLS_H
|