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 1656329 : bool fValid{false};
52 : mutable uint256 cachedHash;
53 :
54 : public:
55 : static constexpr size_t SerSize = _SerSize;
56 :
57 2827640 : explicit CBLSWrapper() = default;
58 :
59 610510 : CBLSWrapper(const CBLSWrapper& ref) = default;
60 490954 : CBLSWrapper& operator=(const CBLSWrapper& ref) = default;
61 485018 : CBLSWrapper(CBLSWrapper&& ref) noexcept
62 242509 : {
63 242509 : std::swap(impl, ref.impl);
64 242509 : std::swap(fValid, ref.fValid);
65 242509 : std::swap(cachedHash, ref.cachedHash);
66 242509 : }
67 225748 : CBLSWrapper& operator=(CBLSWrapper&& ref) noexcept
68 : {
69 225748 : std::swap(impl, ref.impl);
70 225748 : std::swap(fValid, ref.fValid);
71 225748 : std::swap(cachedHash, ref.cachedHash);
72 225748 : return *this;
73 : }
74 :
75 2198388 : virtual ~CBLSWrapper() = default;
76 :
77 65 : bool operator==(const C& r) const
78 : {
79 65 : return fValid == r.fValid && impl == r.impl;
80 : }
81 0 : bool operator!=(const C& r) const
82 : {
83 0 : return !((*this) == r);
84 : }
85 0 : bool operator<(const C& r) const
86 : {
87 0 : return GetHash() < r.GetHash();
88 : }
89 :
90 850682 : bool IsValid() const
91 : {
92 850682 : return fValid;
93 : }
94 :
95 621225 : void Reset()
96 : {
97 621225 : *(static_cast<C*>(this)) = C();
98 621225 : }
99 :
100 577072 : void SetBytes(Span<const uint8_t> vecBytes, const bool specificLegacyScheme)
101 : {
102 577072 : if (vecBytes.size() != SerSize) {
103 0 : Reset();
104 0 : return;
105 : }
106 :
107 47580948 : if (std::ranges::all_of(vecBytes, [](uint8_t c) { return c == 0; })) {
108 576772 : Reset();
109 576772 : } else {
110 : try {
111 300 : impl = ImplType::FromBytes(bls::Bytes(vecBytes.data(), vecBytes.size()), specificLegacyScheme);
112 300 : if (impl == ImplType()) {
113 0 : Reset();
114 0 : cachedHash.SetNull();
115 0 : return;
116 : }
117 300 : fValid = true;
118 300 : } catch (...) {
119 0 : Reset();
120 0 : }
121 : }
122 577072 : cachedHash.SetNull();
123 577072 : }
124 :
125 1 : std::vector<uint8_t> ToByteVector(const bool specificLegacyScheme) const
126 : {
127 1 : if (!fValid) {
128 0 : return std::vector<uint8_t>(SerSize, 0);
129 : }
130 1 : return impl.Serialize(specificLegacyScheme);
131 1 : }
132 :
133 665307 : std::array<uint8_t, SerSize> ToBytes(const bool specificLegacyScheme) const
134 : {
135 665307 : if (!fValid) {
136 664820 : return std::array<uint8_t, SerSize>{};
137 : }
138 487 : return impl.SerializeToArray(specificLegacyScheme);
139 665307 : }
140 :
141 0 : const uint256& GetHash() const
142 : {
143 0 : if (cachedHash.IsNull()) {
144 0 : cachedHash = ::SerializeHash(*this);
145 0 : }
146 0 : return cachedHash;
147 : }
148 :
149 19 : bool SetHexStr(const std::string& str, const bool specificLegacyScheme)
150 : {
151 19 : if (!IsHex(str)) {
152 2 : Reset();
153 2 : return false;
154 : }
155 17 : auto b = ParseHex(str);
156 17 : if (b.size() != SerSize) {
157 4 : Reset();
158 4 : return false;
159 : }
160 13 : SetBytes(b, specificLegacyScheme);
161 13 : return IsValid();
162 19 : }
163 :
164 : inline void Serialize(CSizeComputer& s) const
165 : {
166 : s.seek(SerSize);
167 : }
168 :
169 : template <typename Stream>
170 59030 : inline void Serialize(Stream& s, const bool specificLegacyScheme) const
171 : {
172 59030 : const auto bytes{ToBytes(specificLegacyScheme)};
173 59030 : s.write(AsBytes(Span{bytes.data(), SerSize}));
174 59030 : }
175 :
176 : template <typename Stream>
177 15244 : inline void Serialize(Stream& s) const
178 : {
179 15244 : Serialize(s, bls::bls_legacy_scheme.load());
180 15244 : }
181 :
182 : template <typename Stream>
183 576870 : inline void Unserialize(Stream& s, const bool specificLegacyScheme)
184 : {
185 576870 : std::array<uint8_t, SerSize> vecBytes{};
186 576870 : s.read(AsWritableBytes(Span{vecBytes.data(), SerSize}));
187 576870 : SetBytes(vecBytes, specificLegacyScheme);
188 :
189 576870 : 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 576870 : }
203 :
204 : template <typename Stream>
205 53875 : inline void Unserialize(Stream& s)
206 : {
207 53875 : Unserialize(s, bls::bls_legacy_scheme.load());
208 53875 : }
209 :
210 577043 : inline bool CheckMalleable(Span<uint8_t> vecBytes, const bool specificLegacyScheme) const
211 : {
212 577043 : const auto bytes{ToBytes(specificLegacyScheme)};
213 577043 : 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 577043 : return true;
220 577043 : }
221 :
222 29168 : inline std::string ToString(const bool specificLegacyScheme) const
223 : {
224 29168 : auto buf = ToBytes(specificLegacyScheme);
225 29168 : return HexStr(buf);
226 : }
227 :
228 29061 : inline std::string ToString() const
229 : {
230 29061 : return ToString(bls::bls_legacy_scheme.load());
231 : }
232 : };
233 :
234 : struct CBLSIdImplicit : public uint256
235 : {
236 1196 : CBLSIdImplicit() = default;
237 80 : CBLSIdImplicit(const uint256& id)
238 40 : {
239 40 : memcpy(begin(), id.begin(), sizeof(uint256));
240 80 : }
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 0 : [[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 0 : 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 476 : CBLSSecretKey() = default;
276 30 : explicit CBLSSecretKey(Span<const unsigned char> vecBytes)
277 30 : {
278 : // The second param here is not 'is_legacy', but `modOrder`
279 15 : SetBytes(vecBytes, false);
280 30 : }
281 40568 : CBLSSecretKey(const CBLSSecretKey&) = default;
282 6 : 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 979452 : 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 377788 : CBLSPublicKeyVersionWrapper(CBLSPublicKey& obj, bool legacy)
326 188894 : : obj(obj)
327 188894 : , legacy(legacy)
328 377788 : {}
329 : template <typename Stream>
330 14590 : inline void Serialize(Stream& s) const {
331 14590 : obj.Serialize(s, legacy);
332 14590 : }
333 : template <typename Stream>
334 174304 : inline void Unserialize(Stream& s) {
335 174304 : obj.Unserialize(s, legacy);
336 174304 : }
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 1847600 : 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 1147950 : CBLSSignature(const CBLSSignature&) = default;
354 476138 : 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 755774 : CBLSSignatureVersionWrapper(CBLSSignature& obj, bool legacy)
376 377887 : : obj(obj)
377 377887 : , legacy(legacy)
378 755774 : {}
379 : template <typename Stream>
380 29196 : inline void Serialize(Stream& s) const {
381 29196 : obj.Serialize(s, legacy);
382 29196 : }
383 : template <typename Stream>
384 348691 : inline void Unserialize(Stream& s) {
385 348691 : obj.Unserialize(s, legacy);
386 348691 : }
387 : };
388 :
389 : #ifndef BUILD_BITCOIN_INTERNAL
390 : template<typename BLSObject>
391 : class CBLSLazyWrapper
392 : {
393 : private:
394 : mutable std::mutex mutex;
395 :
396 39974 : mutable std::array<uint8_t, BLSObject::SerSize> vecBytes{};
397 :
398 : mutable BLSObject obj;
399 39974 : mutable bool objInitialized{false};
400 :
401 : // Indicates if the value contained in vecBytes is valid
402 39974 : mutable bool bufValid{false};
403 36925 : mutable bool bufLegacyScheme{true};
404 :
405 : mutable uint256 hash;
406 :
407 : public:
408 12196 : CBLSLazyWrapper() :
409 3049 : bufLegacyScheme(bls::bls_legacy_scheme.load())
410 9147 : {}
411 :
412 147700 : explicit CBLSLazyWrapper(const CBLSLazyWrapper& r)
413 73850 : {
414 36925 : *this = r;
415 73850 : }
416 79948 : virtual ~CBLSLazyWrapper() = default;
417 :
418 36975 : CBLSLazyWrapper& operator=(const CBLSLazyWrapper& r)
419 : {
420 36975 : std::unique_lock<std::mutex> l(r.mutex);
421 36975 : bufValid = r.bufValid;
422 36975 : bufLegacyScheme = r.bufLegacyScheme;
423 36975 : if (r.bufValid) {
424 26707 : vecBytes = r.vecBytes;
425 26707 : } else {
426 10268 : std::fill(vecBytes.begin(), vecBytes.end(), 0);
427 : }
428 36975 : objInitialized = r.objInitialized;
429 36975 : if (r.objInitialized) {
430 159 : obj = r.obj;
431 159 : } else {
432 36816 : obj.Reset();
433 : }
434 36975 : hash = r.hash;
435 : return *this;
436 36975 : }
437 :
438 : inline void Serialize(CSizeComputer& s) const
439 : {
440 : s.seek(BLSObject::SerSize);
441 : }
442 :
443 : template<typename Stream>
444 1344 : inline void Serialize(Stream& s, const bool specificLegacyScheme) const
445 : {
446 1344 : std::unique_lock<std::mutex> l(mutex);
447 1344 : if (!objInitialized && !bufValid) {
448 6 : std::fill(vecBytes.begin(), vecBytes.end(), 0);
449 1344 : } else if (!bufValid || (bufLegacyScheme != specificLegacyScheme)) {
450 67 : vecBytes = obj.ToBytes(specificLegacyScheme);
451 67 : bufValid = true;
452 67 : bufLegacyScheme = specificLegacyScheme;
453 67 : hash.SetNull();
454 67 : }
455 1344 : s.write(MakeByteSpan(vecBytes));
456 1344 : }
457 :
458 : template<typename Stream>
459 771 : inline void Serialize(Stream& s) const
460 : {
461 771 : Serialize(s, bufLegacyScheme);
462 771 : }
463 :
464 : template<typename Stream>
465 258 : inline void Unserialize(Stream& s, const bool specificLegacyScheme) const
466 : {
467 258 : std::unique_lock<std::mutex> l(mutex);
468 258 : s.read(AsWritableBytes(Span{vecBytes.data(), BLSObject::SerSize}));
469 612 : bufValid = std::any_of(vecBytes.begin(), vecBytes.end(), [](uint8_t c) { return c != 0; });
470 258 : bufLegacyScheme = specificLegacyScheme;
471 258 : objInitialized = false;
472 258 : hash.SetNull();
473 258 : }
474 :
475 : template<typename Stream>
476 1 : inline void Unserialize(Stream& s) const
477 : {
478 1 : Unserialize(s, bufLegacyScheme);
479 1 : }
480 :
481 74 : void Set(const BLSObject& _obj, const bool specificLegacyScheme)
482 : {
483 74 : std::unique_lock<std::mutex> l(mutex);
484 74 : bufValid = false;
485 74 : bufLegacyScheme = specificLegacyScheme;
486 74 : objInitialized = true;
487 74 : obj = _obj;
488 74 : hash.SetNull();
489 74 : }
490 191 : const BLSObject& Get() const
491 : {
492 191 : std::unique_lock<std::mutex> l(mutex);
493 191 : static BLSObject invalidObj;
494 191 : if (!bufValid && !objInitialized) {
495 2 : return invalidObj;
496 : }
497 189 : if (!objInitialized) {
498 184 : obj.SetBytes(vecBytes, bufLegacyScheme);
499 184 : if (!obj.IsValid()) {
500 0 : bufValid = false;
501 0 : return invalidObj;
502 : }
503 184 : if (!obj.CheckMalleable(vecBytes, bufLegacyScheme)) {
504 0 : bufValid = false;
505 0 : return invalidObj;
506 : }
507 184 : objInitialized = true;
508 184 : }
509 189 : return obj;
510 191 : }
511 :
512 33768 : bool operator==(const CBLSLazyWrapper& r) const
513 : {
514 33768 : if (&r == this) return true;
515 : {
516 33768 : std::scoped_lock lock(mutex, r.mutex);
517 : // If neither bufValid or objInitialized are set, then the object is the default object.
518 33768 : const bool is_default{!bufValid && !objInitialized};
519 33768 : const bool r_is_default{!r.bufValid && !r.objInitialized};
520 : // If both are default; they are equal.
521 33768 : if (is_default && r_is_default) return true;
522 : // If one is default and the other isn't, we are not equal
523 33584 : if (is_default != r_is_default) return false;
524 :
525 32288 : if (bufValid && r.bufValid && bufLegacyScheme == r.bufLegacyScheme) {
526 32284 : return vecBytes == r.vecBytes;
527 : }
528 4 : if (objInitialized && r.objInitialized) {
529 4 : return obj == r.obj;
530 : }
531 33768 : }
532 0 : return Get() == r.Get();
533 33768 : }
534 :
535 2702 : bool operator!=(const CBLSLazyWrapper& r) const
536 : {
537 2702 : return !(*this == r);
538 : }
539 :
540 47 : uint256 GetHash() const
541 : {
542 47 : std::unique_lock<std::mutex> l(mutex);
543 47 : if (!objInitialized && !bufValid) {
544 0 : std::fill(vecBytes.begin(), vecBytes.end(), 0);
545 0 : hash.SetNull();
546 47 : } else if (!bufValid) {
547 2 : vecBytes = obj.ToBytes(bufLegacyScheme);
548 2 : bufValid = true;
549 2 : hash.SetNull();
550 2 : }
551 47 : if (hash.IsNull()) {
552 47 : CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
553 47 : ss.write(MakeByteSpan(vecBytes));
554 47 : hash = ss.GetHash();
555 47 : }
556 47 : return hash;
557 47 : }
558 :
559 102 : bool IsLegacy() const
560 : {
561 102 : return bufLegacyScheme;
562 : }
563 :
564 1 : void SetLegacy(bool specificLegacyScheme)
565 : {
566 1 : bufLegacyScheme = specificLegacyScheme;
567 1 : }
568 :
569 77 : std::string ToString() const
570 : {
571 77 : 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 1656 : CBLSLazyPublicKeyVersionWrapper(CBLSLazyPublicKey& obj, bool legacy)
583 828 : : obj(obj)
584 828 : , legacy(legacy)
585 1656 : {}
586 : template <typename Stream>
587 572 : inline void Serialize(Stream& s) const {
588 572 : obj.Serialize(s, legacy);
589 572 : }
590 : template <typename Stream>
591 256 : inline void Unserialize(Stream& s) {
592 256 : obj.Unserialize(s, legacy);
593 256 : }
594 : };
595 : #endif
596 :
597 : using BLSVerificationVectorPtr = std::shared_ptr<std::vector<CBLSPublicKey>>;
598 :
599 : bool BLSInit();
600 :
601 : #endif // DASH_CRYPTO_BLS_H
|