From a88ee107d9f066adeb231bc3eeaf837fef092dc2 Mon Sep 17 00:00:00 2001 From: alex v Date: Mon, 16 Oct 2023 23:53:45 +0200 Subject: [PATCH 01/68] BLSCT Tx Factory and Verification --- src/Makefile.am | 11 ++ src/Makefile.test.include | 1 + .../building_block/imp_inner_prod_arg.cpp | 14 +- src/blsct/common.h | 9 +- src/blsct/double_public_key.cpp | 2 +- src/blsct/private_key.cpp | 10 +- src/blsct/private_key.h | 1 + src/blsct/public_keys.cpp | 8 +- src/blsct/public_keys.h | 2 +- .../bulletproofs/range_proof_logic.cpp | 3 +- src/blsct/range_proof/recovered_data.h | 8 + src/blsct/wallet/address.cpp | 10 + src/blsct/wallet/address.h | 6 + src/blsct/wallet/keyman.cpp | 138 +++++++++++++- src/blsct/wallet/keyman.h | 23 ++- src/blsct/wallet/txfactory.cpp | 177 ++++++++++++++++++ src/blsct/wallet/txfactory.h | 59 ++++++ src/blsct/wallet/verification.cpp | 54 ++++++ src/blsct/wallet/verification.h | 16 ++ src/compressor.h | 9 +- src/primitives/transaction.cpp | 17 +- src/primitives/transaction.h | 10 +- src/test/blsct/wallet/txfactory_tests.cpp | 139 ++++++++++++++ src/test/coins_tests.cpp | 58 +++--- src/wallet/receive.cpp | 24 ++- src/wallet/transaction.h | 10 +- src/wallet/types.h | 11 +- src/wallet/wallet.cpp | 34 +++- src/wallet/walletdb.cpp | 15 ++ src/wallet/walletdb.h | 2 + test/functional/rpc_dumptxoutset.py | 3 + 31 files changed, 809 insertions(+), 75 deletions(-) create mode 100644 src/blsct/wallet/txfactory.cpp create mode 100644 src/blsct/wallet/txfactory.h create mode 100644 src/blsct/wallet/verification.cpp create mode 100644 src/blsct/wallet/verification.h create mode 100644 src/test/blsct/wallet/txfactory_tests.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 0b38e99e7e5f8..b51d6abfe8165 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -202,6 +202,8 @@ BITCOIN_CORE_H = \ blsct/wallet/hdchain.h \ blsct/wallet/keyman.h \ blsct/wallet/keyring.h \ + blsct/wallet/txfactory.h \ + blsct/wallet/verification.h \ dandelion.h \ chain.h \ chainparams.h \ @@ -592,9 +594,17 @@ libbitcoin_wallet_a_SOURCES = \ blsct/eip_2333/bls12_381_keygen.cpp \ blsct/private_key.cpp \ blsct/public_key.cpp \ + blsct/building_block/imp_inner_prod_arg.cpp \ + blsct/range_proof/bulletproofs/amount_recovery_request.cpp \ + blsct/range_proof/bulletproofs/amount_recovery_result.cpp \ + blsct/range_proof/bulletproofs/range_proof_logic.cpp \ + blsct/range_proof/bulletproofs/range_proof_with_transcript.cpp \ + blsct/range_proof/msg_amt_cipher.cpp \ blsct/wallet/address.cpp \ blsct/wallet/keyman.cpp \ blsct/wallet/keyring.cpp \ + blsct/wallet/txfactory.cpp \ + blsct/wallet/verification.cpp \ wallet/coincontrol.cpp \ wallet/context.cpp \ wallet/crypter.cpp \ @@ -769,6 +779,7 @@ libbitcoin_consensus_a_SOURCES = \ tinyformat.h \ uint256.cpp \ uint256.h \ + util/moneystr.cpp \ util/strencodings.cpp \ util/strencodings.h \ version.h diff --git a/src/Makefile.test.include b/src/Makefile.test.include index b9a2a85c8508b..0fd5cf751363b 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -195,6 +195,7 @@ BITCOIN_TESTS =\ if ENABLE_WALLET BITCOIN_TESTS += \ + test/blsct/wallet/txfactory_tests.cpp \ wallet/test/feebumper_tests.cpp \ wallet/test/psbt_wallet_tests.cpp \ wallet/test/spend_tests.cpp \ diff --git a/src/blsct/building_block/imp_inner_prod_arg.cpp b/src/blsct/building_block/imp_inner_prod_arg.cpp index 6a238f7755377..a8b0550b18d64 100644 --- a/src/blsct/building_block/imp_inner_prod_arg.cpp +++ b/src/blsct/building_block/imp_inner_prod_arg.cpp @@ -151,12 +151,16 @@ std::optional> ImpInnerProdArg::GenAllRoundXs( using Scalars = Elements; Scalars xs; - for (size_t i = 0; i < num_rounds; ++i) { - fiat_shamir << Ls[i]; - fiat_shamir << Rs[i]; - GEN_FIAT_SHAMIR_VAR(x, fiat_shamir, retry); - xs.Add(x); + + if (Rs.Size() == Ls.Size()) { + for (size_t i = 0; i < std::min(Ls.Size(), num_rounds); ++i) { + fiat_shamir << Ls[i]; + fiat_shamir << Rs[i]; + GEN_FIAT_SHAMIR_VAR(x, fiat_shamir, retry); + xs.Add(x); + } } + return xs; retry: diff --git a/src/blsct/common.h b/src/blsct/common.h index c9f9b663177d4..b4951ec334ef3 100644 --- a/src/blsct/common.h +++ b/src/blsct/common.h @@ -17,8 +17,13 @@ class Common { public: inline static const std::vector BLSCTBALANCE = { - 'B', 'L', 'S', 'C', 'T', 'B', 'A', 'L', 'A', 'N', 'C', 'E' - }; + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + 'B', 'L', 'S', 'C', 'T', 'B', 'A', 'L', 'A', 'N', 'C', 'E', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0'}; static std::vector CDataStreamToVector(const CDataStream& st); diff --git a/src/blsct/double_public_key.cpp b/src/blsct/double_public_key.cpp index 79be02d75b8d2..0cf0d3868eb4a 100644 --- a/src/blsct/double_public_key.cpp +++ b/src/blsct/double_public_key.cpp @@ -19,7 +19,7 @@ DoublePublicKey::DoublePublicKey(const std::vector& keys) CKeyID DoublePublicKey::GetID() const { - return CKeyID(Hash160(GetVch())); + return sk.GetID(); } bool DoublePublicKey::GetViewKey(PublicKey& ret) const diff --git a/src/blsct/private_key.cpp b/src/blsct/private_key.cpp index fb91657b1fbef..1cfddb7c73b34 100644 --- a/src/blsct/private_key.cpp +++ b/src/blsct/private_key.cpp @@ -69,6 +69,11 @@ Signature PrivateKey::SignBalance() const return CoreSign(Common::BLSCTBALANCE); } +Signature PrivateKey::Sign(const uint256& msg) const +{ + return Sign(Message(msg.begin(), msg.end())); +} + Signature PrivateKey::Sign(const Message& msg) const { auto pk = GetPublicKey(); @@ -77,8 +82,9 @@ Signature PrivateKey::Sign(const Message& msg) const return sig; } -bool PrivateKey::VerifyPubKey(const PublicKey& pk) const { +bool PrivateKey::VerifyPubKey(const PublicKey& pk) const +{ return GetPublicKey() == pk; } -} // namespace blsct + } // namespace blsct diff --git a/src/blsct/private_key.h b/src/blsct/private_key.h index 6b3590f17c52b..c61acb81b5aac 100644 --- a/src/blsct/private_key.h +++ b/src/blsct/private_key.h @@ -54,6 +54,7 @@ class PrivateKey Signature SignBalance() const; // Message augmentation scheme + Signature Sign(const uint256& msg) const; Signature Sign(const Message& msg) const; // Core operations diff --git a/src/blsct/public_keys.cpp b/src/blsct/public_keys.cpp index 2c5534dc7add2..2b3595b93515a 100644 --- a/src/blsct/public_keys.cpp +++ b/src/blsct/public_keys.cpp @@ -69,7 +69,7 @@ bool PublicKeys::CoreAggregateVerify(const std::vector& msgs return res == 1; } -bool PublicKeys::VerifyBatch(const std::vector& msgs, const Signature& sig) const +bool PublicKeys::VerifyBatch(const std::vector& msgs, const Signature& sig, const bool& fVerifyTx) const { if (m_pks.size() != msgs.size() || m_pks.size() == 0) { throw std::runtime_error(std::string(__func__) + strprintf( @@ -78,7 +78,11 @@ bool PublicKeys::VerifyBatch(const std::vector& msgs, const std::vector> aug_msgs; auto msg = msgs.begin(); for (auto pk = m_pks.begin(), end = m_pks.end(); pk != end; ++pk, ++msg) { - aug_msgs.push_back(pk->AugmentMessage(*msg)); + if (*msg == blsct::Common::BLSCTBALANCE && fVerifyTx) { + aug_msgs.push_back(*msg); + } else { + aug_msgs.push_back(pk->AugmentMessage(*msg)); + } } return CoreAggregateVerify(aug_msgs, sig); } diff --git a/src/blsct/public_keys.h b/src/blsct/public_keys.h index 81ec6901138ff..d23736fedab0e 100644 --- a/src/blsct/public_keys.h +++ b/src/blsct/public_keys.h @@ -23,7 +23,7 @@ class PublicKeys bool VerifyBalanceBatch(const Signature& sig) const; // Message augmentation scheme - bool VerifyBatch(const std::vector& msgs, const Signature& sig) const; + bool VerifyBatch(const std::vector& msgs, const Signature& sig, const bool& fVerifyTx = false) const; private: // Core operations diff --git a/src/blsct/range_proof/bulletproofs/range_proof_logic.cpp b/src/blsct/range_proof/bulletproofs/range_proof_logic.cpp index c77c729e33fa6..1e226a3e7ab42 100644 --- a/src/blsct/range_proof/bulletproofs/range_proof_logic.cpp +++ b/src/blsct/range_proof/bulletproofs/range_proof_logic.cpp @@ -378,7 +378,7 @@ AmountRecoveryResult RangeProofLogic::RecoverAmounts( // failure if sizes of Ls and Rs differ or Vs is empty auto Ls_Rs_valid = req.Ls.Size() > 0 && req.Ls.Size() == req.Rs.Size(); if (req.Vs.Size() == 0 || !Ls_Rs_valid) { - return AmountRecoveryResult::failure(); + continue; } // recovery can only be done when the number of value commitment is 1 if (req.Vs.Size() != 1) { @@ -425,6 +425,7 @@ AmountRecoveryResult RangeProofLogic::RecoverAmounts( req.nonce.GetHashWithSalt(100), // gamma for vs[0] msg_amt.msg ); + xs.push_back(x); } return { diff --git a/src/blsct/range_proof/recovered_data.h b/src/blsct/range_proof/recovered_data.h index 79dfffc916594..ef34237c542c7 100644 --- a/src/blsct/range_proof/recovered_data.h +++ b/src/blsct/range_proof/recovered_data.h @@ -6,6 +6,7 @@ #define NAVCOIN_BLSCT_ARITH_RANGE_PROOF_RECOVERED_DATA_H #include +#include #include #include @@ -24,10 +25,17 @@ struct RecoveredData const std::string& message ): id{id}, amount{amount}, gamma{gamma}, message{message} {} + RecoveredData() {} + size_t id; CAmount amount; Scalar gamma; std::string message; + + SERIALIZE_METHODS(RecoveredData, obj) + { + READWRITE(obj.amount, obj.gamma, obj.message); + } }; } // namespace range_proof diff --git a/src/blsct/wallet/address.cpp b/src/blsct/wallet/address.cpp index 7da62ab5e4188..a8638538b1db0 100644 --- a/src/blsct/wallet/address.cpp +++ b/src/blsct/wallet/address.cpp @@ -47,4 +47,14 @@ bool SubAddress::IsValid() const { return pk.IsValid(); } + +bool SubAddress::operator==(const SubAddress& rhs) const +{ + return pk == rhs.pk; +} + +bool SubAddress::operator<(const SubAddress& rhs) const +{ + return pk == rhs.pk ? pk < rhs.pk : pk < rhs.pk; +}; } // namespace blsct diff --git a/src/blsct/wallet/address.h b/src/blsct/wallet/address.h index 1a0d0c72a13b3..81c67531e2a76 100644 --- a/src/blsct/wallet/address.h +++ b/src/blsct/wallet/address.h @@ -40,6 +40,7 @@ class SubAddress DoublePublicKey pk; public: + SubAddress(){}; SubAddress(const PrivateKey& viewKey, const PublicKey& spendKey, const SubAddressIdentifier& subAddressId); SubAddress(const DoublePublicKey& pk) : pk(pk){}; @@ -48,6 +49,11 @@ class SubAddress std::string GetString() const; CTxDestination GetDestination() const; DoublePublicKey GetKeys() const { return pk; }; + + SERIALIZE_METHODS(SubAddress, obj) { READWRITE(obj.pk); } + + bool operator==(const SubAddress& rhs) const; + bool operator<(const SubAddress& rhs) const; }; } // namespace blsct diff --git a/src/blsct/wallet/keyman.cpp b/src/blsct/wallet/keyman.cpp index 9ad9d5098b663..6c69b98adce5c 100644 --- a/src/blsct/wallet/keyman.cpp +++ b/src/blsct/wallet/keyman.cpp @@ -341,7 +341,7 @@ void KeyMan::UpdateTimeFirstKey(int64_t nCreateTime) } } -SubAddress KeyMan::GetSubAddress(const SubAddressIdentifier& id) +SubAddress KeyMan::GetSubAddress(const SubAddressIdentifier& id) const { return SubAddress(viewKey, spendPublicKey, id); }; @@ -414,21 +414,99 @@ bool KeyMan::Encrypt(const wallet::CKeyingMaterial& master_key, wallet::WalletBa return true; } -bool KeyMan::IsMine(const blsct::PublicKey& ephemeralKey, const blsct::PublicKey& spendingKey, const uint16_t& viewTag) +CKeyID KeyMan::GetHashId(const blsct::PublicKey& blindingKey, const blsct::PublicKey& spendingKey) const +{ + if (!fViewKeyDefined || !viewKey.IsValid()) + throw std::runtime_error(strprintf("%s: the wallet has no view key available", __func__)); + + auto t = blindingKey.GetG1Point() * viewKey.GetScalar(); + auto dh = MclG1Point::GetBasePoint() * t.GetHashWithSalt(0).Negate(); + auto D_prime = spendingKey.GetG1Point() + dh; + + return PublicKey(D_prime).GetID(); +}; + +blsct::PrivateKey KeyMan::GetSpendingKey() const +{ + if (!fSpendKeyDefined) + throw std::runtime_error(strprintf("%s: the wallet has no spend key available")); + + auto spendingKeyId = m_hd_chain.spend_id; + + PrivateKey ret; + + if (!GetKey(spendingKeyId, ret)) + throw std::runtime_error(strprintf("%s: could not access the spend key", __func__)); + + return ret; +} + +blsct::PrivateKey KeyMan::GetSpendingKeyForOutput(const CTxOut& out) const +{ + auto hashId = GetHashId(out); + + return GetSpendingKeyForOutput(out, hashId); +} + +blsct::PrivateKey KeyMan::GetSpendingKeyForOutput(const CTxOut& out, const CKeyID& hashId) const +{ + SubAddressIdentifier id; + + if (!GetSubAddressId(hashId, id)) + throw std::runtime_error(strprintf("%s: could not read subaddress id", __func__)); + + return GetSpendingKeyForOutput(out, id); +} + +blsct::PrivateKey KeyMan::GetSpendingKeyForOutput(const CTxOut& out, const SubAddressIdentifier& id) const +{ + if (!fViewKeyDefined || !viewKey.IsValid()) + throw std::runtime_error(strprintf("%s: the wallet has no view key available", __func__)); + + auto sk = GetSpendingKey(); + + CHashWriter string(SER_GETHASH, 0); + + string << std::vector(subAddressHeader.begin(), subAddressHeader.end()); + string << viewKey; + string << id.account; + string << id.address; + + MclG1Point t = out.blsctData.blindingKey * viewKey.GetScalar(); + MclScalar ret = t.GetHashWithSalt(0) + sk.GetScalar() + MclScalar(string.GetHash()); + + return ret; +} + +bulletproofs::AmountRecoveryResult KeyMan::RecoverOutputs(const std::vector& outs) +{ + if (!fViewKeyDefined || !viewKey.IsValid()) + return bulletproofs::AmountRecoveryResult::failure(); + + bulletproofs::RangeProofLogic rp; + std::vector> reqs; + reqs.reserve(outs.size()); + + for (size_t i = 0; i < outs.size(); i++) { + CTxOut out = outs[i]; + auto nonce = out.blsctData.blindingKey * viewKey.GetScalar(); + reqs.push_back(bulletproofs::AmountRecoveryRequest::of({out.blsctData.rangeProof}, nonce)); + } + + return rp.RecoverAmounts(reqs); +} + +bool KeyMan::IsMine(const blsct::PublicKey& blindingKey, const blsct::PublicKey& spendingKey, const uint16_t& viewTag) { if (!fViewKeyDefined || !viewKey.IsValid()) return false; CHashWriter hash(SER_GETHASH, PROTOCOL_VERSION); - hash << (ephemeralKey.GetG1Point() * viewKey.GetScalar()); + hash << (blindingKey.GetG1Point() * viewKey.GetScalar()); - if (viewTag != (hash.GetHash().GetUint64(0) & 0xFF)) - return false; + if (viewTag != (hash.GetHash().GetUint64(0) & 0xFFFF)) return false; - auto t = ephemeralKey.GetG1Point() * viewKey.GetScalar(); - auto dh = MclG1Point::GetBasePoint() * t.GetHashWithSalt(0).Invert(); - auto D_prime = spendingKey.GetG1Point() + dh; - auto hashId = PublicKey(D_prime).GetID(); + auto hashId = GetHashId(blindingKey, spendingKey); { LOCK(cs_KeyStore); @@ -458,6 +536,45 @@ bool KeyMan::HaveSubAddress(const CKeyID& hashId) const return mapSubAddresses.count(hashId) > 0; } +bool KeyMan::GetSubAddress(const CKeyID& hashId, SubAddress& address) const +{ + LOCK(cs_KeyStore); + if (!HaveSubAddress(hashId)) return false; + address = GetSubAddress(mapSubAddresses.at(hashId)); + return true; +} + +bool KeyMan::GetSubAddressId(const CKeyID& hashId, SubAddressIdentifier& id) const +{ + LOCK(cs_KeyStore); + if (!HaveSubAddress(hashId)) return false; + id = mapSubAddresses.at(hashId); + return true; +} + +void KeyMan::LoadSubAddressStr(const SubAddress& subAddress, const CKeyID& hashId) +{ + LOCK(cs_KeyStore); + mapSubAddressesStr[subAddress] = hashId; +} + +bool KeyMan::AddSubAddressStr(const SubAddress& subAddress, const CKeyID& hashId) +{ + LOCK(cs_KeyStore); + wallet::WalletBatch batch(m_storage.GetDatabase()); + AssertLockHeld(cs_KeyStore); + + mapSubAddressesStr[subAddress] = hashId; + + return batch.WriteSubAddressStr(subAddress, hashId); +} + +bool KeyMan::HaveSubAddressStr(const SubAddress& subAddress) const +{ + LOCK(cs_KeyStore); + return mapSubAddressesStr.count(subAddress) > 0; +} + SubAddress KeyMan::GenerateNewSubAddress(const uint64_t& account, SubAddressIdentifier& id) { if (m_hd_chain.nSubAddressCounter.count(account) == 0) @@ -487,6 +604,9 @@ SubAddress KeyMan::GenerateNewSubAddress(const uint64_t& account, SubAddressIden if (!AddSubAddress(subAddress.GetKeys().GetID(), id)) throw std::runtime_error(std::string(__func__) + ": AddSubAddress failed"); + if (!AddSubAddressStr(subAddress, subAddress.GetKeys().GetID())) + throw std::runtime_error(std::string(__func__) + ": AddSubAddressStr failed"); + return subAddress; } diff --git a/src/blsct/wallet/keyman.h b/src/blsct/wallet/keyman.h index 9fcc029af7fd7..d99b8fb0e6a0d 100644 --- a/src/blsct/wallet/keyman.h +++ b/src/blsct/wallet/keyman.h @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -47,10 +50,12 @@ class KeyMan : public Manager, public KeyRing using CryptedKeyMap = std::map>>; using SubAddressMap = std::map; + using SubAddressStrMap = std::map; using SubAddressPoolMapSet = std::map>; CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); SubAddressMap mapSubAddresses GUARDED_BY(cs_KeyStore); + SubAddressStrMap mapSubAddressesStr GUARDED_BY(cs_KeyStore); SubAddressPoolMapSet setSubAddressPool GUARDED_BY(cs_KeyStore); SubAddressPoolMapSet setSubAddressReservePool GUARDED_BY(cs_KeyStore); @@ -104,7 +109,7 @@ class KeyMan : public Manager, public KeyRing bool CheckDecryptionKey(const wallet::CKeyingMaterial& master_key, bool accept_no_keys); SubAddress GenerateNewSubAddress(const uint64_t& account, SubAddressIdentifier& id); - SubAddress GetSubAddress(const SubAddressIdentifier& id = {0, 0}); + SubAddress GetSubAddress(const SubAddressIdentifier& id = {0, 0}) const; util::Result GetNewDestination(const uint64_t& account = 0); /* Set the HD chain model (chain child index counters) and writes it to the database */ @@ -121,13 +126,25 @@ class KeyMan : public Manager, public KeyRing bool DeleteKeys(); /** Detect ownership of outputs **/ - bool IsMine(const CTxOut& txout) { return IsMine(txout.blsctData.ephemeralKey, txout.blsctData.spendingKey, txout.blsctData.viewTag); }; - bool IsMine(const blsct::PublicKey& ephemeralKey, const blsct::PublicKey& spendingKey, const uint16_t& viewTag); + bool IsMine(const CTxOut& txout) { return IsMine(txout.blsctData.blindingKey, txout.blsctData.spendingKey, txout.blsctData.viewTag); }; + bool IsMine(const blsct::PublicKey& blindingKey, const blsct::PublicKey& spendingKey, const uint16_t& viewTag); + CKeyID GetHashId(const CTxOut& txout) const { return GetHashId(txout.blsctData.blindingKey, txout.blsctData.spendingKey); } + CKeyID GetHashId(const blsct::PublicKey& blindingKey, const blsct::PublicKey& spendingKey) const; + blsct::PrivateKey GetSpendingKey() const; + blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out) const; + blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out, const CKeyID& id) const; + blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out, const SubAddressIdentifier& id) const; + bulletproofs::AmountRecoveryResult RecoverOutputs(const std::vector& outs); /** SubAddress keypool */ void LoadSubAddress(const CKeyID& hashId, const SubAddressIdentifier& index); bool AddSubAddress(const CKeyID& hashId, const SubAddressIdentifier& index); bool HaveSubAddress(const CKeyID& hashId) const EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); + bool GetSubAddress(const CKeyID& hashId, SubAddress& address) const; + bool GetSubAddressId(const CKeyID& hashId, SubAddressIdentifier& subAddId) const; + void LoadSubAddressStr(const SubAddress& subAddress, const CKeyID& hashId); + bool AddSubAddressStr(const SubAddress& subAddress, const CKeyID& hashId); + bool HaveSubAddressStr(const SubAddress& subAddress) const; bool NewSubAddressPool(const uint64_t& account = 0); bool TopUp(const unsigned int& size = 0); bool TopUpAccount(const uint64_t& account, const unsigned int& size = 0); diff --git a/src/blsct/wallet/txfactory.cpp b/src/blsct/wallet/txfactory.cpp new file mode 100644 index 0000000000000..f7866432ad30d --- /dev/null +++ b/src/blsct/wallet/txfactory.cpp @@ -0,0 +1,177 @@ +// Copyright (c) 2023 The Navcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +using T = Mcl; +using Point = T::Point; +using Points = Elements; +using Scalar = T::Scalar; +using Scalars = Elements; + +namespace blsct { + +void UnsignedOutput::GenerateKeys(Scalar blindingKey, DoublePublicKey destKeys) +{ + out.blsctData.ephemeralKey = PrivateKey(blindingKey).GetPoint(); + + Point vk, sk; + + if (!destKeys.GetViewKey(vk)) { + throw std::runtime_error(strprintf("%s: could not get view key from destination address\n", __func__)); + } + + if (!destKeys.GetSpendKey(sk)) { + throw std::runtime_error(strprintf("%s: could not get spend key from destination address\n", __func__)); + } + + out.blsctData.blindingKey = sk * blindingKey; + + auto rV = vk * blindingKey; + + out.blsctData.spendingKey = sk + (PrivateKey(Scalar(rV.GetHashWithSalt(0))).GetPoint()); +} + +UnsignedOutput TxFactory::CreateOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId) +{ + auto ret = UnsignedOutput(); + + ret.out.tokenId = tokenId; + ret.out.scriptPubKey = CScript(OP_TRUE); + + Scalars vs; + vs.Add(nAmount); + + auto destKeys = destination.GetKeys(); + auto blindingKey = Scalar::Rand(); + + ret.blindingKey = blindingKey; + + Points nonces; + Point vk; + + if (!destKeys.GetViewKey(vk)) { + throw std::runtime_error(strprintf("%s: could not get view key from destination address\n", __func__)); + } + + auto nonce = vk * blindingKey; + nonces.Add(nonce); + + ret.value = nAmount; + ret.gamma = nonce.GetHashWithSalt(100); + + std::vector memo{sMemo.begin(), sMemo.end()}; + + bulletproofs::RangeProofLogic rp; + auto p = rp.Prove(vs, nonce, memo, tokenId); + + ret.out.blsctData.rangeProof = p; + + CHashWriter hash(SER_GETHASH, PROTOCOL_VERSION); + hash << nonce; + + ret.GenerateKeys(blindingKey, destKeys); + ret.out.blsctData.viewTag = (hash.GetHash().GetUint64(0) & 0xFFFF); + + return ret; +} + +void TxFactory::AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId) +{ + UnsignedOutput out; + out = TxFactory::CreateOutput(destination, nAmount, sMemo, tokenId); + + if (nAmounts.count(tokenId) <= 0) + nAmounts[tokenId] = {0, 0}; + + nAmounts[tokenId].nFromOutputs += nAmount; + + if (vOutputs.count(tokenId) <= 0) + vOutputs[tokenId] = std::vector(); + + vOutputs[tokenId].push_back(out); +} + +bool TxFactory::AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint) +{ + Coin coin; + + if (!cache.GetCoin(outpoint, coin)) + return false; + + auto recoveredInfo = km->RecoverOutputs(std::vector{coin.out}); + + if (!recoveredInfo.is_completed) + return false; + + if (vInputs.count(coin.out.tokenId) <= 0) + vInputs[coin.out.tokenId] = std::vector(); + + vInputs[coin.out.tokenId].push_back({CTxIn(outpoint), recoveredInfo.amounts[0].amount, recoveredInfo.amounts[0].gamma, km->GetSpendingKeyForOutput(coin.out)}); + + if (nAmounts.count(coin.out.tokenId) <= 0) + nAmounts[coin.out.tokenId] = {0, 0}; + + nAmounts[coin.out.tokenId].nFromInputs += recoveredInfo.amounts[0].amount; + + return true; +} + +std::optional TxFactory::BuildTx() +{ + CAmount nFee = 200000 * (vInputs.size() + vOutputs.size() + 1); + + while (true) { + CMutableTransaction tx; + Scalar gammaAcc; + std::map mapChange; + std::vector txSigs; + + for (auto& amounts : nAmounts) { + if (amounts.second.nFromInputs < amounts.second.nFromOutputs) + return std::nullopt; + mapChange[amounts.first] = amounts.second.nFromInputs - amounts.second.nFromOutputs - nFee; + } + + for (auto& in_ : vInputs) { + for (auto& in : in_.second) { + tx.vin.push_back(in.in); + gammaAcc = gammaAcc + in.gamma; + txSigs.push_back(in.sk.Sign(in.in.GetHash())); + } + } + + for (auto& out_ : vOutputs) { + for (auto& out : out_.second) { + tx.vout.push_back(out.out); + gammaAcc = gammaAcc - out.gamma; + txSigs.push_back(PrivateKey(out.blindingKey).Sign(out.out.GetHash())); + } + } + + for (auto& change : mapChange) { + auto changeOutput = CreateOutput(blsct::SubAddress(std::get(km->GetNewDestination(0).value())), change.second, "Change", change.first); + tx.vout.push_back(changeOutput.out); + gammaAcc = gammaAcc - changeOutput.gamma; + txSigs.push_back(PrivateKey(changeOutput.blindingKey).Sign(changeOutput.out.GetHash())); + } + + if (nFee == (long long)(200000 * (tx.vin.size() + tx.vout.size() + 1))) { + CTxOut fee_out{nFee, CScript(OP_RETURN)}; + auto blindingKey = PrivateKey(Scalar::Rand()); + fee_out.blsctData.ephemeralKey = blindingKey.GetPublicKey().GetG1Point(); + txSigs.push_back(blindingKey.Sign(fee_out.GetHash())); + tx.vout.push_back(fee_out); + txSigs.push_back(PrivateKey(gammaAcc).SignBalance()); + tx.txSig = Signature::Aggregate(txSigs); + return tx; + } + + nFee = 200000 * (tx.vin.size() + tx.vout.size() + 1); + } + + return std::nullopt; +} + +} // namespace blsct \ No newline at end of file diff --git a/src/blsct/wallet/txfactory.h b/src/blsct/wallet/txfactory.h new file mode 100644 index 0000000000000..d12a636878180 --- /dev/null +++ b/src/blsct/wallet/txfactory.h @@ -0,0 +1,59 @@ +// Copyright (c) 2023 The Navcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef TXFACTORY_H +#define TXFACTORY_H + +#include +#include +#include + +using T = Mcl; +using Point = T::Point; +using Points = Elements; +using Scalar = T::Scalar; +using Scalars = Elements; + +namespace blsct { +struct UnsignedOutput { + CTxOut out; + Scalar blindingKey; + Scalar value; + Scalar gamma; + + void GenerateKeys(Scalar blindingKey, DoublePublicKey destKeys); +}; + +struct UnsignedInput { + CTxIn in; + Scalar value; + Scalar gamma; + PrivateKey sk; +}; + +struct Amounts { + CAmount nFromInputs; + CAmount nFromOutputs; +}; + +class TxFactory +{ +private: + KeyMan* km; + CMutableTransaction tx; + std::map> vOutputs; + std::map> vInputs; + std::map nAmounts; + +public: + TxFactory(KeyMan* km) : km(km){}; + + static UnsignedOutput CreateOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId()); + void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId()); + bool AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint); + std::optional BuildTx(); +}; +} // namespace blsct + +#endif // TXFACTORY_H \ No newline at end of file diff --git a/src/blsct/wallet/verification.cpp b/src/blsct/wallet/verification.cpp new file mode 100644 index 0000000000000..4968f630aae48 --- /dev/null +++ b/src/blsct/wallet/verification.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2023 The Navcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +namespace blsct { +bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view) +{ + if (!view.HaveInputs(tx)) { + std::cout << "Unknown inputs\n"; + return false; + } + + std::vector vMessages; + std::vector vPubKeys; + MclG1Point balanceKey; + + for (auto& in : tx.vin) { + Coin coin; + + if (!view.GetCoin(in.prevout, coin)) { + std::cout << "Unknown input\n"; + return false; + } + + vPubKeys.push_back(coin.out.blsctData.spendingKey); + auto in_hash = in.GetHash(); + vMessages.push_back(Message(in_hash.begin(), in_hash.end())); + balanceKey = balanceKey + coin.out.blsctData.rangeProof.Vs[0]; + } + + for (auto& out : tx.vout) { + vPubKeys.push_back(out.blsctData.ephemeralKey); + auto out_hash = out.GetHash(); + vMessages.push_back(Message(out_hash.begin(), out_hash.end())); + + if (out.IsBLSCT()) { + balanceKey = balanceKey - out.blsctData.rangeProof.Vs[0]; + } else { + range_proof::GeneratorsFactory gf; + + TokenId token_id; + range_proof::Generators gen = gf.GetInstance(token_id); + balanceKey = balanceKey - (gen.G * MclScalar(out.nValue)); + } + } + + vMessages.push_back(blsct::Common::BLSCTBALANCE); + vPubKeys.push_back(balanceKey); + + return PublicKeys{vPubKeys}.VerifyBatch(vMessages, tx.txSig, true); +} +} // namespace blsct \ No newline at end of file diff --git a/src/blsct/wallet/verification.h b/src/blsct/wallet/verification.h new file mode 100644 index 0000000000000..eb3594d2ebc7c --- /dev/null +++ b/src/blsct/wallet/verification.h @@ -0,0 +1,16 @@ +// Copyright (c) 2023 The Navcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BLSCT_VERIFICATION_H +#define BLSCT_VERIFICATION_H + +#include +#include +#include +#include + +namespace blsct { +bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view); +} +#endif // BLSCT_VERIFICATION_H \ No newline at end of file diff --git a/src/compressor.h b/src/compressor.h index 0968454679b56..c016636f06ab6 100644 --- a/src/compressor.h +++ b/src/compressor.h @@ -109,10 +109,11 @@ struct AmountCompression } }; -/** wrapper for CTxOut that provides a more compact serialization */ -struct TxOutCompression -{ - FORMATTER_METHODS(CTxOut, obj) { READWRITE(Using(obj.nValue), Using(obj.scriptPubKey)); } +/** wrapper for CTxOut that provides a more compact serialization + * TODO: Compress BLSCT fields + */ +struct TxOutCompression { + FORMATTER_METHODS(CTxOut, obj) { READWRITE(obj); } }; #endif // BITCOIN_COMPRESSOR_H diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 3b32e50a998c3..00838512b017f 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -51,6 +51,11 @@ std::string CTxIn::ToString() const return str; } +uint256 CTxIn::GetHash() const +{ + return SerializeHash(*this, SER_GETHASH); +} + CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn, TokenId tokenIdIn) { nValue = nValueIn; @@ -60,9 +65,15 @@ CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn, TokenId tokenIdI std::string CTxOut::ToString() const { - return strprintf("CTxOut(scriptPubKey=%s, spendingKey=%s, blindingKey=%s, ephemeralKey=%s%s)", HexStr(scriptPubKey).substr(0, 30), - HexStr(blsctData.spendingKey.GetVch()), HexStr(blsctData.blindingKey.GetVch()), HexStr(blsctData.ephemeralKey.GetVch()), - tokenId.IsNull() ? "" : strprintf(", tokenId=%s", tokenId.ToString())); + return strprintf("CTxOut(scriptPubKey=%s%s%s%s)", HexStr(scriptPubKey).substr(0, 30), + IsBLSCT() ? strprintf(", spendingKey=%s, blindingKey=%s, ephemeralKey=%s", HexStr(blsctData.spendingKey.GetVch()), HexStr(blsctData.blindingKey.GetVch()), HexStr(blsctData.ephemeralKey.GetVch())) : "", + tokenId.IsNull() ? "" : strprintf(", tokenId=%s", tokenId.ToString()), + IsBLSCT() ? "" : strprintf(", nAmount=%s", FormatMoney(nValue))); +} + +uint256 CTxOut::GetHash() const +{ + return SerializeHash(*this, SER_GETHASH); } CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0) {} diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 42469e24ba7d5..fe47740ccaa30 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -16,6 +16,7 @@ #include