diff --git a/src/Makefile.am b/src/Makefile.am index 534e4a48ba337..f320bf41ad15b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -183,6 +183,10 @@ BITCOIN_CORE_H = \ blsct/set_mem_proof/set_mem_proof.h \ blsct/set_mem_proof/set_mem_proof_prover.h \ blsct/signature.h \ + blsct/wallet/address.h \ + blsct/wallet/hdchain.h \ + blsct/wallet/keyman.h \ + blsct/wallet/keyring.h \ dandelion.h \ chain.h \ chainparams.h \ @@ -556,6 +560,12 @@ endif libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ + blsct/eip_2333/bls12_381_keygen.cpp \ + blsct/private_key.cpp \ + blsct/public_key.cpp \ + blsct/wallet/address.cpp \ + blsct/wallet/keyman.cpp \ + blsct/wallet/keyring.cpp \ wallet/coincontrol.cpp \ wallet/context.cpp \ wallet/crypter.cpp \ @@ -736,6 +746,8 @@ libbitcoin_common_a_SOURCES = \ base58.cpp \ bech32.cpp \ blsct/arith/elements.cpp \ + blsct/double_public_key.cpp \ + blsct/public_key.cpp \ chainparams.cpp \ coins.cpp \ common/args.cpp \ diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 46e2156430315..febf66b4cd489 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -41,6 +41,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman) argsman.AddArg("-dumpfile=", "When used with 'dump', writes out the records to this file. When used with 'createfromdump', loads the records into a new wallet.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); argsman.AddArg("-debug=", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-descriptors", "Create descriptors wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-blsct", "Create blsct wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-legacy", "Create legacy wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-format=", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); diff --git a/src/blsct/arith/mcl/mcl_g1point.cpp b/src/blsct/arith/mcl/mcl_g1point.cpp index 3a1aed0ca7295..678b9b76330b7 100644 --- a/src/blsct/arith/mcl/mcl_g1point.cpp +++ b/src/blsct/arith/mcl/mcl_g1point.cpp @@ -172,8 +172,9 @@ std::vector MclG1Point::GetVch() const { std::vector b(SERIALIZATION_SIZE); if (mclBnG1_serialize(&b[0], SERIALIZATION_SIZE, &m_p) == 0) { - MclG1Point ret; - return ret.GetVch(); + MclG1Point unity; + auto ret = unity.GetVch(); + return ret; } return b; } diff --git a/src/blsct/double_public_key.cpp b/src/blsct/double_public_key.cpp index 5c02318f3274f..1ef61932f4830 100644 --- a/src/blsct/double_public_key.cpp +++ b/src/blsct/double_public_key.cpp @@ -6,11 +6,27 @@ namespace blsct { +DoublePublicKey::DoublePublicKey(const std::vector& keys) { + if (keys.size() != SIZE) return; + std::vector vkData(SIZE/2); + std::vector skData(SIZE/2); + std::copy(keys.begin(), keys.begin()+SIZE/2, vkData.begin()); + std::copy(keys.begin()+SIZE/2, keys.end(), skData.begin()); + vk = vkData; + sk = skData; +} + CKeyID DoublePublicKey::GetID() const { return CKeyID(Hash160(GetVch())); } +bool DoublePublicKey::GetViewKey(PublicKey& ret) const +{ + ret = vk; + return true; +} + bool DoublePublicKey::GetViewKey(Point& ret) const { try { @@ -22,6 +38,12 @@ bool DoublePublicKey::GetViewKey(Point& ret) const return true; } +bool DoublePublicKey::GetSpendKey(PublicKey& ret) const +{ + ret = sk; + return true; +} + bool DoublePublicKey::GetSpendKey(Point& ret) const { try { @@ -38,6 +60,12 @@ bool DoublePublicKey::operator==(const DoublePublicKey& rhs) const return vk == rhs.vk && sk == rhs.sk; } +bool DoublePublicKey::operator<(const DoublePublicKey& rhs) const +{ + return this->GetVkVch() == rhs.GetVkVch() ? this->GetSkVch() < rhs.GetSkVch() : this->GetVkVch() < rhs.GetVkVch(); +}; + + bool DoublePublicKey::IsValid() const { return vk.IsValid() && sk.IsValid(); diff --git a/src/blsct/double_public_key.h b/src/blsct/double_public_key.h index 452cf457e36f8..b40b00d0aecc6 100644 --- a/src/blsct/double_public_key.h +++ b/src/blsct/double_public_key.h @@ -24,8 +24,10 @@ class DoublePublicKey static constexpr size_t SIZE = 48 * 2; DoublePublicKey() {} + DoublePublicKey(const PublicKey& vk_, const PublicKey& sk_) : vk(vk_.GetVch()), sk(sk_.GetVch()) {} DoublePublicKey(const Point& vk_, const Point& sk_) : vk(vk_.GetVch()), sk(sk_.GetVch()) {} DoublePublicKey(const std::vector& vk_, const std::vector& sk_) : vk(vk_), sk(sk_) {} + DoublePublicKey(const std::vector& keys); SERIALIZE_METHODS(DoublePublicKey, obj) { READWRITE(obj.vk.GetVch(), obj.sk.GetVch()); } @@ -35,7 +37,11 @@ class DoublePublicKey bool GetViewKey(Point& ret) const; bool GetSpendKey(Point& ret) const; + bool GetViewKey(PublicKey& ret) const; + bool GetSpendKey(PublicKey& ret) const; + bool operator==(const DoublePublicKey& rhs) const; + bool operator<(const DoublePublicKey& rhs) const; bool IsValid() const; diff --git a/src/blsct/private_key.cpp b/src/blsct/private_key.cpp index bf4c7394447ae..249b87321f833 100644 --- a/src/blsct/private_key.cpp +++ b/src/blsct/private_key.cpp @@ -10,9 +10,6 @@ namespace blsct { PrivateKey::PrivateKey(Scalar k_) { - if (k_.IsZero()) { - throw std::runtime_error("Private key needs to be a non-zero scalar"); - } k.resize(PrivateKey::SIZE); std::vector v = k_.GetVch(); memcpy(k.data(), &v.front(), k.size()); @@ -36,12 +33,14 @@ PrivateKey::Point PrivateKey::GetPoint() const PublicKey PrivateKey::GetPublicKey() const { - return PublicKey(GetPoint()); + auto point = GetPoint(); + return point; } PrivateKey::Scalar PrivateKey::GetScalar() const { - return Scalar(std::vector(k.begin(), k.end())); + auto ret = std::vector(k.begin(), k.end()); + return ret; } bool PrivateKey::IsValid() const @@ -78,4 +77,8 @@ Signature PrivateKey::Sign(const Message& msg) const return sig; } +bool PrivateKey::VerifyPubKey(const PublicKey& pk) const { + return GetPublicKey() == pk; +} + } // namespace blsct diff --git a/src/blsct/private_key.h b/src/blsct/private_key.h index 60279f5250c65..6b3590f17c52b 100644 --- a/src/blsct/private_key.h +++ b/src/blsct/private_key.h @@ -25,11 +25,21 @@ class PrivateKey public: static constexpr size_t SIZE = 32; - PrivateKey() { k.clear(); } + PrivateKey() { k.resize(SIZE); } PrivateKey(Scalar k_); PrivateKey(CPrivKey k_); - SERIALIZE_METHODS(PrivateKey, obj) { READWRITE(std::vector(obj.k.begin(), obj.k.end())); } + template + void Serialize(Stream& s) const + { + s.write(MakeByteSpan(k)); + } + + template + void Unserialize(Stream& s) + { + s.read(MakeWritableByteSpan(k)); + } bool operator==(const PrivateKey& rhs) const; @@ -38,6 +48,7 @@ class PrivateKey Scalar GetScalar() const; bool IsValid() const; void SetToZero(); + bool VerifyPubKey(const PublicKey& pk) const; // Basic scheme Signature SignBalance() const; @@ -48,8 +59,11 @@ class PrivateKey // Core operations Signature CoreSign(const Message& msg) const; - friend class CCryptoKeyStore; - friend class CBasicKeyStore; + //! Simple read-only vector-like interface. + unsigned int size() const { return (IsValid() ? k.size() : 0); } + const std::byte* data() const { return reinterpret_cast(k.data()); } + const unsigned char* begin() const { return k.data(); } + const unsigned char* end() const { return k.data() + size(); } }; } diff --git a/src/blsct/public_key.cpp b/src/blsct/public_key.cpp index e7a161fd70f39..126d56b2e9c62 100644 --- a/src/blsct/public_key.cpp +++ b/src/blsct/public_key.cpp @@ -43,6 +43,11 @@ bool PublicKey::operator==(const PublicKey& rhs) const return GetVch() == rhs.GetVch(); } +bool PublicKey::operator!=(const PublicKey& rhs) const +{ + return GetVch() != rhs.GetVch(); +} + bool PublicKey::IsValid() const { if (data.size() == 0) return false; diff --git a/src/blsct/public_key.h b/src/blsct/public_key.h index cc5cecb4f2265..bc3a131b07f30 100644 --- a/src/blsct/public_key.h +++ b/src/blsct/public_key.h @@ -36,12 +36,15 @@ class PublicKey std::string ToString() const; bool operator==(const PublicKey& rhs) const; + bool operator!=(const PublicKey& rhs) const; bool IsValid() const; bool GetG1Point(Point& ret) const; std::vector GetVch() const; + bool operator<(const PublicKey& b) const { return this->GetVch() < b.GetVch(); }; + blsPublicKey ToBlsPublicKey() const; std::vector AugmentMessage(const Message& msg) const; diff --git a/src/blsct/wallet/address.cpp b/src/blsct/wallet/address.cpp new file mode 100644 index 0000000000000..ce677feda5ab4 --- /dev/null +++ b/src/blsct/wallet/address.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 { +SubAddress::SubAddress(const PrivateKey &viewKey, const PublicKey &spendKey, const SubAddressIdentifier &subAddressId) +{ + if(!viewKey.IsValid() || !spendKey.IsValid()) + return; + + CHashWriter string(SER_GETHASH, 0); + + string << std::vector(subAddressHeader.begin(), subAddressHeader.end()); + string << viewKey; + string << subAddressId.account; + string << subAddressId.address; + + // m = Hs(a || i) + // M = m*G + // D = B + M + // C = a*D + MclScalar m{string.GetHash()}; + MclG1Point M, B; + + if (!PrivateKey(m).GetPublicKey().GetG1Point(M)) + return; + + if (!spendKey.GetG1Point(B)) + return; + + MclG1Point D = M + B; + auto C = D * viewKey.GetScalar(); + pk = DoublePublicKey(C, D); +} + +std::string SubAddress::GetString() const +{ + return EncodeDestination(pk); +} + +CTxDestination SubAddress::GetDestination() const +{ + if (!IsValid()) + return CNoDestination(); + return pk; +} + +bool SubAddress::IsValid() const +{ + return pk.IsValid(); +} +} diff --git a/src/blsct/wallet/address.h b/src/blsct/wallet/address.h new file mode 100644 index 0000000000000..02bb65221e114 --- /dev/null +++ b/src/blsct/wallet/address.h @@ -0,0 +1,37 @@ +// 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 NAVCOIN_BLSCT_ADDRESS_H +#define NAVCOIN_BLSCT_ADDRESS_H + +#include +#include +#include +#include + +namespace blsct { +static const std::string subAddressHeader = "SubAddress\0"; + +struct SubAddressIdentifier { + uint64_t account; + uint64_t address; +}; + +class SubAddress +{ +private: + DoublePublicKey pk; +public: + SubAddress(const PrivateKey &viewKey, const PublicKey &spendKey, const SubAddressIdentifier &subAddressId); + SubAddress(const DoublePublicKey& pk) : pk(pk) {}; + + bool IsValid() const; + + std::string GetString() const; + CTxDestination GetDestination() const; + DoublePublicKey GetKeys() const { return pk; }; +}; +} + +#endif // NAVCOIN_BLSCT_ADDRESS_H diff --git a/src/blsct/wallet/hdchain.h b/src/blsct/wallet/hdchain.h new file mode 100644 index 0000000000000..6f45e55f82318 --- /dev/null +++ b/src/blsct/wallet/hdchain.h @@ -0,0 +1,48 @@ +// Copyright (c) 2023 The Navcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BLSCTHDCHAIN_H +#define BLSCTHDCHAIN_H + +#include +#include + +/* simple HD chain data model */ +namespace blsct { +class HDChain +{ +public: + CKeyID seed_id; //!< seed hash160 + CKeyID spend_id; //!< spend hash160 + CKeyID view_id; //!< view hash160 + CKeyID token_id; //!< token hash160 + + static const int VERSION_HD_BASE = 1; + static const int CURRENT_VERSION = VERSION_HD_BASE; + int nVersion; + + HDChain() { SetNull(); } + + SERIALIZE_METHODS(HDChain, obj) + { + READWRITE(obj.nVersion, obj.seed_id, obj.spend_id, obj.view_id, obj.token_id); + } + + void SetNull() + { + nVersion = HDChain::CURRENT_VERSION; + seed_id.SetNull(); + spend_id.SetNull(); + view_id.SetNull(); + token_id.SetNull(); + } + + bool operator==(const HDChain& chain) const + { + return seed_id == chain.seed_id && spend_id == chain.spend_id && view_id == chain.view_id && token_id == chain.token_id; + } +}; +} + +#endif // BLSCTHDCHAIN_H diff --git a/src/blsct/wallet/keyman.cpp b/src/blsct/wallet/keyman.cpp new file mode 100644 index 0000000000000..23ee953a465ae --- /dev/null +++ b/src/blsct/wallet/keyman.cpp @@ -0,0 +1,416 @@ +// 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 KeyMan::IsHDEnabled() const +{ + return !m_hd_chain.seed_id.IsNull(); +} + +bool KeyMan::CanGenerateKeys() const +{ + // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD) + LOCK(cs_KeyStore); + return IsHDEnabled(); +} + +bool KeyMan::AddKeyPubKeyInner(const PrivateKey& key, const PublicKey &pubkey) +{ + LOCK(cs_KeyStore); + if (!m_storage.HasEncryptionKeys()) { + return KeyRing::AddKeyPubKey(key, pubkey); + } + + if (m_storage.IsLocked()) { + return false; + } + + std::vector vchCryptedSecret; + auto keyVch = key.GetScalar().GetVch(); + wallet::CKeyingMaterial vchSecret(keyVch.begin(), keyVch.end()); + if (!wallet::EncryptSecret(m_storage.GetEncryptionKey(), vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedKey(pubkey, vchCryptedSecret)) { + return false; + } + return true; +} + +bool KeyMan::AddKeyPubKey(const PrivateKey& secret, const PublicKey &pubkey) +{ + LOCK(cs_KeyStore); + wallet::WalletBatch batch(m_storage.GetDatabase()); + return KeyMan::AddKeyPubKeyWithDB(batch, secret, pubkey); +} + +bool KeyMan::AddViewKey(const PrivateKey& secret, const PublicKey& pubkey) +{ + LOCK(cs_KeyStore); + wallet::WalletBatch batch(m_storage.GetDatabase()); + AssertLockHeld(cs_KeyStore); + + if (!fViewKeyDefined) { + KeyRing::AddViewKey(secret, pubkey); + + return batch.WriteViewKey(pubkey, secret, + mapKeyMetadata[pubkey.GetID()]); + } + m_storage.UnsetBlankWalletFlag(batch); + return true; +} + +bool KeyMan::AddSpendKey(const PublicKey& pubkey) +{ + LOCK(cs_KeyStore); + wallet::WalletBatch batch(m_storage.GetDatabase()); + AssertLockHeld(cs_KeyStore); + + if (!fSpendKeyDefined) { + KeyRing::AddSpendKey(pubkey); + + if (!batch.WriteSpendKey(pubkey)) + return false; + } + + m_storage.UnsetBlankWalletFlag(batch); + return true; +} + +bool KeyMan::AddKeyPubKeyWithDB(wallet::WalletBatch& batch, const PrivateKey& secret, const PublicKey& pubkey) +{ + AssertLockHeld(cs_KeyStore); + + // Make sure we aren't adding private keys to private key disabled wallets + assert(!m_storage.IsWalletFlagSet(wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + + bool needsDB = !encrypted_batch; + if (needsDB) { + encrypted_batch = &batch; + } + if (!AddKeyPubKeyInner(secret, pubkey)) { + if (needsDB) encrypted_batch = nullptr; + return false; + } + if (needsDB) encrypted_batch = nullptr; + + if (!m_storage.HasEncryptionKeys()) { + return batch.WriteKey(pubkey, + secret, + mapKeyMetadata[pubkey.GetID()]); + } + m_storage.UnsetBlankWalletFlag(batch); + return true; +} + +bool KeyMan::LoadCryptedKey(const PublicKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid) +{ + // Set fDecryptionThoroughlyChecked to false when the checksum is invalid + if (!checksum_valid) { + fDecryptionThoroughlyChecked = false; + } + + return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); +} + +bool KeyMan::AddCryptedKeyInner(const PublicKey &vchPubKey, const std::vector &vchCryptedSecret) +{ + LOCK(cs_KeyStore); + for (const KeyMap::value_type& mKey : mapKeys) + { + const PrivateKey &key = mKey.second; + PublicKey pubKey = key.GetPublicKey(); + } + assert(mapKeys.empty()); + + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + return true; +} + +bool KeyMan::AddCryptedKey(const PublicKey &vchPubKey, + const std::vector &vchCryptedSecret) +{ + if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) + return false; + { + LOCK(cs_KeyStore); + if (encrypted_batch) + return encrypted_batch->WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + else + return wallet::WalletBatch(m_storage.GetDatabase()).WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); + } +} + +PrivateKey KeyMan::GenerateNewSeed() +{ + assert(!m_storage.IsWalletFlagSet(wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + PrivateKey key(BLS12_381_KeyGen::derive_master_SK(MclScalar::Rand(true).GetVch())); + return key; +} + +void KeyMan::LoadHDChain(const blsct::HDChain& chain) +{ + LOCK(cs_KeyStore); + m_hd_chain = chain; +} + +void KeyMan::AddHDChain(const blsct::HDChain& chain) +{ + LOCK(cs_KeyStore); + // Store the new chain + if (!wallet::WalletBatch(m_storage.GetDatabase()).WriteBLSCTHDChain(chain)) { + throw std::runtime_error(std::string(__func__) + ": writing chain failed"); + } + // When there's an old chain, add it as an inactive chain as we are now rotating hd chains + if (!m_hd_chain.seed_id.IsNull()) { + AddInactiveHDChain(m_hd_chain); + } + + m_hd_chain = chain; +} + +void KeyMan::AddInactiveHDChain(const blsct::HDChain& chain) +{ + LOCK(cs_KeyStore); + assert(!chain.seed_id.IsNull()); + m_inactive_hd_chains[chain.seed_id] = chain; +} + + +void KeyMan::SetHDSeed(const PrivateKey& key) +{ + LOCK(cs_KeyStore); + // store the keyid (hash160) together with + // the child index counter in the database + // as a hdchain object + blsct::HDChain newHdChain; + + auto seed = key.GetPublicKey(); + auto scalarMasterKey = key.GetScalar(); + auto childKey = BLS12_381_KeyGen::derive_child_SK(scalarMasterKey, 130); + auto transactionKey = BLS12_381_KeyGen::derive_child_SK(childKey, 0); + //auto blindingKey = BLS12_381_KeyGen::derive_child_SK(childKey, 1); + auto tokenKey = PrivateKey(BLS12_381_KeyGen::derive_child_SK(childKey, 2)); + auto viewKey = PrivateKey(BLS12_381_KeyGen::derive_child_SK(transactionKey, 0)); + auto spendKey = PrivateKey(BLS12_381_KeyGen::derive_child_SK(transactionKey, 1)); + + newHdChain.nVersion = blsct::HDChain::VERSION_HD_BASE; + newHdChain.seed_id = key.GetPublicKey().GetID(); + newHdChain.spend_id = spendKey.GetPublicKey().GetID(); + newHdChain.view_id = viewKey.GetPublicKey().GetID(); + newHdChain.token_id = tokenKey.GetPublicKey().GetID(); + + int64_t nCreationTime = GetTime(); + + wallet::CKeyMetadata spendMetadata(nCreationTime); + wallet::CKeyMetadata viewMetadata(nCreationTime); + wallet::CKeyMetadata tokenMetadata(nCreationTime); + + spendMetadata.hdKeypath = "spend"; + spendMetadata.has_key_origin = false; + spendMetadata.hd_seed_id = newHdChain.spend_id; + + viewMetadata.hdKeypath = "view"; + viewMetadata.has_key_origin = false; + viewMetadata.hd_seed_id = newHdChain.view_id; + + tokenMetadata.hdKeypath = "token"; + tokenMetadata.has_key_origin = false; + tokenMetadata.hd_seed_id = newHdChain.token_id; + + // mem store the metadata + mapKeyMetadata[newHdChain.spend_id] = spendMetadata; + mapKeyMetadata[newHdChain.view_id] = viewMetadata; + mapKeyMetadata[newHdChain.token_id] = tokenMetadata; + + // write the keys to the database + if (!AddKeyPubKey(key, seed)) + throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); + + if (!AddKeyPubKey(spendKey, spendKey.GetPublicKey())) + throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); + + if (!AddSpendKey(spendKey.GetPublicKey())) + throw std::runtime_error(std::string(__func__) + ": AddSpendKey failed"); + + if (!AddViewKey(viewKey, viewKey.GetPublicKey())) + throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); + + if (!AddKeyPubKey(tokenKey, tokenKey.GetPublicKey())) + throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); + + AddHDChain(newHdChain); + NotifyCanGetAddressesChanged(); + wallet::WalletBatch batch(m_storage.GetDatabase()); + m_storage.UnsetBlankWalletFlag(batch); +} + +bool KeyMan::SetupGeneration(bool force) +{ + if ((CanGenerateKeys() && !force) || m_storage.IsLocked()) { + return false; + } + + SetHDSeed(GenerateNewSeed()); + /*if (!NewKeyPool()) { + return false; + }*/ + return true; +} + +bool KeyMan::CheckDecryptionKey(const wallet::CKeyingMaterial& master_key, bool accept_no_keys) +{ + { + LOCK(cs_KeyStore); + assert(mapKeys.empty()); + + bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys + bool keyFail = false; + CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); + wallet::WalletBatch batch(m_storage.GetDatabase()); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const PublicKey &vchPubKey = (*mi).second.first; + const std::vector &vchCryptedSecret = (*mi).second.second; + PrivateKey key; + if (!wallet::DecryptKey(master_key, vchCryptedSecret, vchPubKey, key)) + { + keyFail = true; + break; + } + keyPass = true; + if (fDecryptionThoroughlyChecked) + break; + else { + // Rewrite these encrypted keys with checksums + batch.WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]); + } + } + if (keyPass && keyFail) + { + LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); + throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); + } + if (keyFail || (!keyPass && !accept_no_keys)) + return false; + fDecryptionThoroughlyChecked = true; + } + return true; +} + +void KeyMan::LoadKeyMetadata(const CKeyID& keyID, const wallet::CKeyMetadata& meta) +{ + LOCK(cs_KeyStore); + UpdateTimeFirstKey(meta.nCreateTime); + mapKeyMetadata[keyID] = meta; +} + +bool KeyMan::LoadKey(const PrivateKey& key, const PublicKey &pubkey) +{ + return AddKeyPubKeyInner(key, pubkey); +} + +bool KeyMan::LoadViewKey(const PrivateKey& key, const PublicKey &pubkey) +{ + return KeyRing::AddViewKey(key, pubkey); +} + +/** + * Update wallet first key creation time. This should be called whenever keys + * are added to the wallet, with the oldest key creation time. + */ +void KeyMan::UpdateTimeFirstKey(int64_t nCreateTime) +{ + AssertLockHeld(cs_KeyStore); + if (nCreateTime <= 1) { + // Cannot determine birthday information, so set the wallet birthday to + // the beginning of time. + nTimeFirstKey = 1; + } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) { + nTimeFirstKey = nCreateTime; + } +} + +SubAddress KeyMan::GetAddress(const SubAddressIdentifier& id) +{ + return SubAddress(viewKey, spendPublicKey, id); +}; + +bool KeyMan::HaveKey(const CKeyID &id) const +{ + LOCK(cs_KeyStore); + if (!m_storage.HasEncryptionKeys()) { + return KeyRing::HaveKey(id); + } + return mapCryptedKeys.count(id) > 0; +} + +bool KeyMan::GetKey(const CKeyID &id, PrivateKey& keyOut) const +{ + LOCK(cs_KeyStore); + if (!m_storage.HasEncryptionKeys()) { + return KeyRing::GetKey(id, keyOut); + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(id); + if (mi != mapCryptedKeys.end()) + { + const PublicKey &vchPubKey = (*mi).second.first; + const std::vector &vchCryptedSecret = (*mi).second.second; + return wallet::DecryptKey(m_storage.GetEncryptionKey(), vchCryptedSecret, vchPubKey, keyOut); + } + return false; +} + +bool KeyMan::DeleteRecords() +{ + LOCK(cs_KeyStore); + wallet::WalletBatch batch(m_storage.GetDatabase()); + return batch.EraseRecords(wallet::DBKeys::BLSCT_TYPES); +} + +bool KeyMan::DeleteKeys() +{ + LOCK(cs_KeyStore); + wallet::WalletBatch batch(m_storage.GetDatabase()); + return batch.EraseRecords(wallet::DBKeys::BLSCTKEY_TYPES); +} + +bool KeyMan::Encrypt(const wallet::CKeyingMaterial& master_key, wallet::WalletBatch* batch) +{ + LOCK(cs_KeyStore); + encrypted_batch = batch; + if (!mapCryptedKeys.empty()) { + encrypted_batch = nullptr; + return false; + } + + KeyMap keys_to_encrypt; + keys_to_encrypt.swap(mapKeys); // Clear mapKeys so AddCryptedKeyInner will succeed. + for (const KeyMap::value_type& mKey : keys_to_encrypt) + { + const PrivateKey &key = mKey.second; + PublicKey pubKey = key.GetPublicKey(); + wallet::CKeyingMaterial vchSecret(key.begin(), key.end()); + std::vector vchCryptedSecret; + if (!wallet::EncryptSecret(master_key, vchSecret, pubKey.GetHash(), vchCryptedSecret)) { + encrypted_batch = nullptr; + return false; + } + if (!AddCryptedKey(pubKey, vchCryptedSecret)) { + encrypted_batch = nullptr; + return false; + } + } + encrypted_batch = nullptr; + return true; +} +} diff --git a/src/blsct/wallet/keyman.h b/src/blsct/wallet/keyman.h new file mode 100644 index 0000000000000..afec640c0a456 --- /dev/null +++ b/src/blsct/wallet/keyman.h @@ -0,0 +1,116 @@ +// 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 NAVCOIN_BLSCT_KEYMAN_H +#define NAVCOIN_BLSCT_KEYMAN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blsct { +class Manager +{ +protected: + wallet::WalletStorage& m_storage; + +public: + explicit Manager(wallet::WalletStorage& storage) : m_storage(storage) {} + virtual ~Manager() {}; + + virtual bool SetupGeneration(bool force = false) { return false; } + + /* Returns true if HD is enabled */ + virtual bool IsHDEnabled() const { return false; } +}; + +class KeyMan : public Manager, public KeyRing { +private: + blsct::HDChain m_hd_chain; + std::unordered_map m_inactive_hd_chains; + + bool AddKeyPubKeyInner(const PrivateKey& key, const PublicKey &pubkey); + bool AddCryptedKeyInner(const PublicKey &vchPubKey, const std::vector &vchCryptedSecret); + + wallet::WalletBatch *encrypted_batch GUARDED_BY(cs_KeyStore) = nullptr; + + using CryptedKeyMap = std::map>>; + + CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); + + int64_t nTimeFirstKey GUARDED_BY(cs_KeyStore) = 0; + + bool fDecryptionThoroughlyChecked = true; +public: + KeyMan(wallet::WalletStorage& storage) : Manager(storage), KeyRing() {} + + bool SetupGeneration(bool force = false) override; + bool IsHDEnabled() const override; + + /* Returns true if the wallet can generate new keys */ + bool CanGenerateKeys() const; + + /* Generates a new HD seed (will not be activated) */ + PrivateKey GenerateNewSeed(); + + /* Set the current HD seed (will reset the chain child index counters) + Sets the seed's version based on the current wallet version (so the + caller must ensure the current wallet version is correct before calling + this function). */ + void SetHDSeed(const PrivateKey& key); + + //! Adds a key to the store, and saves it to disk. + bool AddKeyPubKey(const PrivateKey& key, const PublicKey &pubkey) override; + bool AddViewKey(const PrivateKey& key, const PublicKey &pubkey) override; + bool AddSpendKey(const PublicKey &pubkey) override; + + //! Adds a key to the store, without saving it to disk (used by LoadWallet) + bool LoadKey(const PrivateKey& key, const PublicKey &pubkey); + bool LoadViewKey(const PrivateKey& key, const PublicKey &pubkey); + //! Adds an encrypted key to the store, and saves it to disk. + bool AddCryptedKey(const PublicKey &vchPubKey, const std::vector &vchCryptedSecret); + //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) + bool LoadCryptedKey(const PublicKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid); + bool AddKeyPubKeyWithDB(wallet::WalletBatch& batch, const PrivateKey& secret, const PublicKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); + + /* KeyRing overrides */ + bool HaveKey(const CKeyID &address) const override; + bool GetKey(const CKeyID &address, PrivateKey& keyOut) const override; + + bool Encrypt(const wallet::CKeyingMaterial& master_key, wallet::WalletBatch* batch); + bool CheckDecryptionKey(const wallet::CKeyingMaterial& master_key, bool accept_no_keys); + + SubAddress GetAddress(const SubAddressIdentifier& id = {0,0}); + + /* Set the HD chain model (chain child index counters) and writes it to the database */ + void AddHDChain(const blsct::HDChain& chain); + void LoadHDChain(const blsct::HDChain& chain); + const blsct::HDChain& GetHDChain() const { return m_hd_chain; } + void AddInactiveHDChain(const blsct::HDChain& chain); + + //! Load metadata (used by LoadWallet) + void LoadKeyMetadata(const CKeyID& keyID, const wallet::CKeyMetadata &metadata); + void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); + + bool DeleteRecords(); + bool DeleteKeys(); + + /** Keypool has new keys */ + boost::signals2::signal NotifyCanGetAddressesChanged; + + // Map from Key ID to key metadata. + std::map mapKeyMetadata GUARDED_BY(cs_KeyStore); +}; +} + +#endif // NAVCOIN_BLSCT_KEYMAN_H diff --git a/src/blsct/wallet/keyring.cpp b/src/blsct/wallet/keyring.cpp new file mode 100644 index 0000000000000..73f0499812c3a --- /dev/null +++ b/src/blsct/wallet/keyring.cpp @@ -0,0 +1,48 @@ +// 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 KeyRing::AddKeyPubKey(const PrivateKey& key, const PublicKey &pubkey) +{ + LOCK(cs_KeyStore); + mapKeys[pubkey.GetID()] = key; + return true; +} + +bool KeyRing::AddViewKey(const PrivateKey& key, const PublicKey &pubkey) +{ + LOCK(cs_KeyStore); + viewKey = key; + viewPublicKey = key.GetPublicKey(); + fViewKeyDefined = true; + return true; +} + +bool KeyRing::AddSpendKey(const PublicKey &pubkey) +{ + LOCK(cs_KeyStore); + spendPublicKey = pubkey; + fSpendKeyDefined = true; + return true; +} + +bool KeyRing::HaveKey(const CKeyID &id) const +{ + LOCK(cs_KeyStore); + return mapKeys.count(id) > 0; +} + +bool KeyRing::GetKey(const CKeyID &address, PrivateKey &keyOut) const +{ + LOCK(cs_KeyStore); + KeyMap::const_iterator mi = mapKeys.find(address); + if (mi != mapKeys.end()) { + keyOut = mi->second; + return true; + } + return false; +} +} diff --git a/src/blsct/wallet/keyring.h b/src/blsct/wallet/keyring.h new file mode 100644 index 0000000000000..43b307a6752cf --- /dev/null +++ b/src/blsct/wallet/keyring.h @@ -0,0 +1,45 @@ +// 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 KEYRING_H +#define KEYRING_H + +#include +#include +#include +#include + +namespace blsct { +class KeyRing { +public: + using KeyMap = std::map; + + /** + * Map of key id to unencrypted private keys known by the signing provider. + * Map may be empty if the provider has another source of keys, like an + * encrypted store. + */ + KeyMap mapKeys GUARDED_BY(cs_KeyStore); + mutable RecursiveMutex cs_KeyStore; + + PrivateKey viewKey; + PublicKey viewPublicKey; + PublicKey spendPublicKey; + + virtual bool AddKeyPubKey(const PrivateKey& key, const PublicKey &pubkey); + virtual bool AddKey(const PrivateKey &key) { return AddKeyPubKey(key, key.GetPublicKey()); } + virtual bool AddViewKey(const PrivateKey &key, const PublicKey& pubkey); + virtual bool AddSpendKey(const PublicKey &pubkey); + + virtual bool HaveKey(const CKeyID &id) const; + virtual bool GetKey(const CKeyID &id, PrivateKey &keyOut) const; + + virtual ~KeyRing() = default; + + bool fSpendKeyDefined{false}; + bool fViewKeyDefined{false}; +}; +} + +#endif // KEYRING_H diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index ec731407da5ac..77069e802fd49 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -141,6 +141,7 @@ class CMainParams : public CChainParams { vSeeds.emplace_back("seed.bitcoin.wiz.biz."); // Jason Maurice base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,0); + base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,5); base58Prefixes[SECRET_KEY] = std::vector(1,128); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E}; @@ -249,6 +250,7 @@ class CTestNetParams : public CChainParams { vSeeds.emplace_back("testnet-seed.bluematt.me."); // Just a static list of stable node(s), only supports x9 base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); + base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; @@ -374,6 +376,7 @@ class SigNetParams : public CChainParams { vFixedSeeds.clear(); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); + base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; @@ -502,6 +505,7 @@ class CRegTestParams : public CChainParams }; base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); + base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h index ad0b49a885daa..a0fc84393a69f 100644 --- a/src/kernel/chainparams.h +++ b/src/kernel/chainparams.h @@ -82,6 +82,7 @@ class CChainParams SECRET_KEY, EXT_PUBLIC_KEY, EXT_SECRET_KEY, + BLSCT_ADDRESS, MAX_BASE58_TYPES }; diff --git a/src/key_io.cpp b/src/key_io.cpp index 4659a595447e7..82a794c891e60 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -24,6 +24,16 @@ class DestinationEncoder public: explicit DestinationEncoder(const CChainParams& params) : m_params(params) {} + std::string operator()(const blsct::DoublePublicKey& id) const + { + std::vector data = m_params.Base58Prefix(CChainParams::BLSCT_ADDRESS); + auto vchView = id.GetVkVch(); + auto vchSpend = id.GetSkVch(); + data.insert(data.end(), vchView.begin(), vchView.end()); + data.insert(data.end(), vchSpend.begin(), vchSpend.end()); + return EncodeBase58Check(data); + } + std::string operator()(const PKHash& id) const { std::vector data = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); @@ -114,7 +124,16 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par return CNoDestination(); } else if (!is_bech32) { // Try Base58 decoding without the checksum, using a much larger max length - if (!DecodeBase58(str, data, 100)) { + if (DecodeBase58Check(str, data, 100)) { + // base58-encoded BLSCT addresses. + const std::vector& blsct_prefix = params.Base58Prefix(CChainParams::BLSCT_ADDRESS); + std::vector blsctKeysData; + blsctKeysData.resize(2*blsct::PublicKey::SIZE); + if (data.size() == blsctKeysData.size() + blsct_prefix.size() && std::equal(blsct_prefix.begin(), blsct_prefix.end(), data.begin())) { + std::copy(data.begin() + blsct_prefix.size(), data.end(), blsctKeysData.begin()); + return blsct::DoublePublicKey(blsctKeysData); + } + } if (!DecodeBase58(str, data, 100)) { error_str = "Invalid or unsupported Segwit (Bech32) or Base58 encoding."; } else { error_str = "Invalid checksum or length of Base58 address (P2PKH or P2SH)"; diff --git a/src/key_io.h b/src/key_io.h index 07b80c4b859fe..22f941796daad 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_KEY_IO_H #define BITCOIN_KEY_IO_H +#include #include #include #include diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index bff9bc2b29ca1..ffaaf474a247e 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -295,6 +295,15 @@ class DescribeAddressVisitor return obj; } + UniValue operator()(const blsct::DoublePublicKey& pk) const + { + UniValue obj(UniValue::VOBJ); + obj.pushKV("isblsct", true); + obj.pushKV("spendKey", HexStr(pk.GetSkVch())); + obj.pushKV("viewKey", HexStr(pk.GetVkVch())); + return obj; + } + UniValue operator()(const WitnessUnknown& id) const { UniValue obj(UniValue::VOBJ); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 7c4a05b6e6951..0178d37c4544f 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -328,6 +328,11 @@ class CScriptVisitor { return CScript() << CScript::EncodeOP_N(id.version) << std::vector(id.program, id.program + id.length); } + + CScript operator()(const blsct::DoublePublicKey& pk) const + { + return CScript() << OP_1; + } }; } // namespace diff --git a/src/script/standard.h b/src/script/standard.h index 18cf5c8c88423..e1a097281bca9 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -7,6 +7,7 @@ #define BITCOIN_SCRIPT_STANDARD_H #include +#include #include #include