/* * This file is part of the Flowee project * Copyright (C) 2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "BackupSyncModuleInfo.h" #include "BackupNFCHandler.h" #include "BackupAccountInfo.h" #include "QMLData.h" #include "WalletData.h" #include #include // for the qmlRegisterType #include #include #include #include namespace Module { enum Tags { WalletSegment, PrivateKey, LastSavedTime, // long, epoch based timestamp NeedsExport, // bool // LastSaveHash, }; } namespace { static BackupSyncModuleInfo *g_instance = nullptr; } BackupSyncModuleInfo *BackupSyncModuleInfo::instance() { assert(g_instance); return g_instance; } ModuleInfo * BackupSyncModuleInfo::build() { assert(g_instance == nullptr); g_instance = new BackupSyncModuleInfo(); BackupSyncModuleInfo *info = g_instance; info->setId("backupSyncModule"); info->setTitle(tr("Backup Sync")); info->setDescription(tr("Transaction comments cloud backup.")); // Provide a way to configure backup on a certain wallet. auto *section = new ModuleSection(ModuleSection::CustomSectionType, info); section->setSectionId("walletOptions"); if (ModuleManager::frontEnd() == ModuleManager::Mobile) section->setStartQMLFile("qrc:/backup-sync/WalletOptions.qml"); else section->setStartQMLFile("qrc:/backup-sync/WalletOptions-desktop.qml"); info->addSection(section); // Starting point to initiate storing a wallet on an NFC tag auto *section2 = new ModuleSection(ModuleSection::CustomSectionType, info); section2->setSectionId("storeOnNfc"); section2->setStartQMLFile("qrc:/backup-sync/WalletToNFC.qml"); info->addSection(section2); qmlRegisterType("Flowee.org.pay.backup", 1, 0, "NFC"); qmlRegisterType("Flowee.org.pay.backup", 1, 0, "BackupAccountInfo"); return info; } BackupSyncModuleInfo::BackupSyncModuleInfo() { connect(FloweePay::instance(), &FloweePay::newNfcTagData, this, &BackupSyncModuleInfo::onTagRead, Qt::QueuedConnection); connect(FloweePay::instance(), &FloweePay::nfcStatusChanged, this, &BackupSyncModuleInfo::nfcAvailableChanged); #if 0 QTimer::singleShot(5000, this, [=]() { auto data = QByteArray::fromHex("0"); std::shared_ptr tag = std::make_shared(); NFCTagData::Record record; record.mimeType = "application/bch-seed"; auto pool = Streaming::pool(data.size()); pool->write(data.constData(), data.size()); record.payload = pool->commit(); tag->addRecord(record); onTagRead(tag); }); #endif } void BackupSyncModuleInfo::loadSettings(const Streaming::ConstBuffer &data) { // delay the basic setup a little to ensure that the // FloweePay instance has been created. QTimer::singleShot(0, this, [=]() { // Delay more to ensure all wallets are created. connect(FloweePay::instance(), &FloweePay::loadComplete, this, [=]() { // now load wallet data from blob Streaming::MessageParser parser(data); const auto wallets = FloweePay::instance()->wallets(); WalletData *wd = nullptr; while (parser.next() == Streaming::FoundTag) { if (parser.tag() == Module::WalletSegment) { wd = nullptr; // avoid corruption when a wallet was deleted. int id = parser.intData(); for (const std::shared_ptr &wallet : wallets) { if (wallet->segment()->segmentId() == id) { wd = new WalletData(wallet, this); m_walletData.append(wd); break; } } } else if (parser.tag() == Module::PrivateKey) { if (wd) wd->setPrivKeyData(parser.bytesDataBuffer()); } else if (parser.tag() == Module::LastSavedTime) { if (wd) wd->setLastSavedTime(parser.longData()); } else if (parser.tag() == Module::NeedsExport) { if (wd && parser.boolData()) wd->markDirty(); } } m_loaded = true; setupWallets(); connect (FloweePay::instance(), &FloweePay::walletsChanged, this, [=]() { setupWallets(); }); }); }); } Streaming::ConstBuffer BackupSyncModuleInfo::saveSettings(std::shared_ptr &pool) const { pool->reserve(m_walletData.size() * 25); Streaming::MessageBuilder builder(pool); for (auto *wd : m_walletData) { builder.add(Module::WalletSegment, wd->wallet()->segment()->segmentId()); if (wd->wallet()->encryption() != Wallet::NotEncrypted && wd->privKey().isValid()) { const auto &key = wd->privKey(); builder.addByteArray(Module::PrivateKey, key.begin(), key.size()); } builder.add(Module::LastSavedTime, static_cast(wd->lastSavedTime())); if (wd->needsSave()) builder.add(Module::NeedsExport, true); } return builder.buffer(); } void BackupSyncModuleInfo::setupWallets() { bool addedOne = false; for (const auto &wallet : FloweePay::instance()->wallets()) { bool known = false; for (const auto *wd : std::as_const(m_walletData)) { if (wd->wallet() == wallet) { known = true; break; } } if (!known) { if (!wallet->isHDWallet()) continue; // ignore archived wallets. if (wallet->segment()->priority() >= PrivacySegment::OnlyManual) continue; auto path = HDMasterKey::deriveFromString(wallet->derivationPath().toStdString()); if (path.size() != 3) { logInfo(9313) << "Notice non standard derived wallet" << wallet->name(); continue; } addedOne = true; // wallets get this enabled by default. AccountConfig accountConfig(wallet); accountConfig.setCloudStoreDetails(true); // create our wrapper. auto *wd = new WalletData(wallet, this); m_walletData.append(wd); // as this is either an existing wallet because this feature is new, // or this is a new wallet just imported or created, lets check for prior backups. wd->restore(WalletData::JustRestore); } } if (addedOne) emit walletDataChanged(); } ModuleManager *BackupSyncModuleInfo::mmanager() const { return qobject_cast(parent()); } QList BackupSyncModuleInfo::walletData() const { return m_walletData; } bool BackupSyncModuleInfo::nfcAvailable() const { return FloweePay::instance()->nfcStatus() != WalletEnums::NFC_Unsupported; } void BackupSyncModuleInfo::onTagRead(const std::shared_ptr &data) { assert(data.get()); logInfo() << "Found an NFC tag with" << data.get()->records().size() << "record(s)"; auto records = data->records(); assert(!records.isEmpty()); if (records.first().mimeType == "application/bch-seed") { const auto payload = records.first().payload; logInfo() << "NFC: Going to try to restore the seed."; auto *qmlData = new QMLData(this); auto *backup = SeedsBackup::fromData(payload); if (backup && !backup->paths().isEmpty()) { qmlData->setSeeds(backup); const auto seedPhrase = backup->generateSeedPhrase(); qmlData->setSeed(seedPhrase); const auto paths = backup->paths(); // iterate through existing wallets and check if the seed matches one of the wallets for (const auto &wallet : FloweePay::instance()->wallets()) { if (wallet->isHDWallet()) { if (wallet->hdWalletMnemonic() == seedPhrase) { bool fullMatch = false; for (const auto &item : paths) { if (item.path == wallet->derivationPath()) { fullMatch = true; break; } } qmlData->addWallet(wallet, fullMatch); } } } if (qmlData->seedKnown()) { mmanager()->requestAddPage("qrc:/backup-sync/WalletExists.qml", qmlData); return; } mmanager()->requestAddPage("qrc:/backup-sync/ImportSeed.qml", qmlData); return; } else { logWarning() << "NFC failed restoring, data didn't parse or too new version"; // TODO FloweePay::setNotification() // would be nice to show an "NFC tag data not understood" or so delete qmlData; } } // TODO // if we get here, some other mime type was found, likely initiating a payment. }