2022-07-07 20:54:57 +02:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Flowee project
|
|
|
|
|
* Copyright (C) 2021 Tom Zander <tom@flowee.org>
|
|
|
|
|
*
|
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
#include "TestHDWallets.h"
|
|
|
|
|
#include <Logger.h>
|
|
|
|
|
|
|
|
|
|
#include <apputils/Mnemonic.h>
|
|
|
|
|
#include <utils/HDMasterKey.h>
|
2022-07-07 20:57:06 +02:00
|
|
|
#include <utils/HDMasterPubkey.h>
|
2022-07-07 20:54:57 +02:00
|
|
|
#include <cashaddr.h>
|
|
|
|
|
|
|
|
|
|
TestHDWallets::TestHDWallets()
|
|
|
|
|
{
|
|
|
|
|
ECC_Start(); // neded for crypto module, deriving pubkeys for instance.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TestHDWallets::~TestHDWallets()
|
|
|
|
|
{
|
|
|
|
|
ECC_Stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestHDWallets::hdwallet()
|
|
|
|
|
{
|
|
|
|
|
QFETCH(QString, mnemonic);
|
2023-10-19 16:13:32 +03:00
|
|
|
QFETCH(HDMasterKey::MnemonicType, mnemonicType);
|
2022-07-07 20:54:57 +02:00
|
|
|
QFETCH(QString, masterPrivKey);
|
|
|
|
|
QFETCH(QString, derivation);
|
|
|
|
|
QFETCH(QString, bitcoinAddress);
|
|
|
|
|
|
2023-10-19 16:13:32 +03:00
|
|
|
HDMasterKey mkey = HDMasterKey::fromMnemonic(mnemonic.toStdString(), mnemonicType);
|
2022-07-07 20:54:57 +02:00
|
|
|
QVERIFY(mkey.isValid());
|
2024-04-29 21:13:03 +02:00
|
|
|
QCOMPARE(QString::fromStdString(mkey.toXPrivString("m/")), masterPrivKey);
|
2024-04-29 21:19:24 +02:00
|
|
|
|
|
|
|
|
// second way of creation should give same result.
|
|
|
|
|
HDMasterKey mkey2 = HDMasterKey::fromXPriv(masterPrivKey.toStdString());
|
|
|
|
|
QVERIFY(mkey2.isValid());
|
|
|
|
|
QVERIFY(mkey2 == mkey);
|
|
|
|
|
|
2022-07-07 20:54:57 +02:00
|
|
|
auto derivedKey = mkey.derive(derivation.toStdString());
|
|
|
|
|
QVERIFY(derivedKey.isValid());
|
|
|
|
|
auto id = derivedKey.getPubKey().getKeyId();
|
|
|
|
|
auto address = CashAddress::encodeCashAddr("bitcoincash",
|
|
|
|
|
{ CashAddress::PUBKEY_TYPE,
|
|
|
|
|
std::vector<uint8_t>(id.begin(), id.end())});
|
|
|
|
|
QCOMPARE(QString::fromStdString(address), bitcoinAddress);
|
2022-07-07 20:57:06 +02:00
|
|
|
|
|
|
|
|
const auto derivationPathOrig = HDMasterKey::deriveFromString(derivation.toStdString());
|
|
|
|
|
auto iter = derivationPathOrig.begin();
|
|
|
|
|
for (size_t index = 0; index < derivationPathOrig.size(); ++index) {
|
|
|
|
|
if ((derivationPathOrig[index] & HDMasterKey::Hardened) == 0)
|
|
|
|
|
break;
|
|
|
|
|
++iter;
|
|
|
|
|
}
|
|
|
|
|
// Split into the hardened part and the rest.
|
|
|
|
|
std::vector<uint32_t> hardDerivPath(derivationPathOrig.begin(), iter);
|
|
|
|
|
|
|
|
|
|
// See if the HDMasterPubkey also comes to the same conclusion using the xpub
|
2022-12-11 20:05:46 +01:00
|
|
|
try {
|
|
|
|
|
auto mPubKeyTestnet = HDMasterPubkey::fromXPub(mkey.toXPubString(hardDerivPath, HDMasterKey::Testnet));
|
|
|
|
|
QVERIFY(mPubKeyTestnet.isValid());
|
|
|
|
|
QCOMPARE(mPubKeyTestnet.chain(), HDMasterPubkey::Testnet);
|
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
|
QFAIL(e.what());
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 18:04:14 +02:00
|
|
|
const auto xpub = mkey.toXPubString(hardDerivPath, HDMasterKey::MainChain);
|
|
|
|
|
auto mPubKey = HDMasterPubkey::fromXPub(xpub);
|
2022-07-07 20:57:06 +02:00
|
|
|
QVERIFY(mPubKey.isValid());
|
|
|
|
|
QCOMPARE(mPubKey.chain(), HDMasterPubkey::MainChain);
|
2022-07-11 18:04:14 +02:00
|
|
|
QCOMPARE(mPubKey.toString(), xpub);
|
2022-07-07 20:57:06 +02:00
|
|
|
|
2022-07-08 12:58:41 +02:00
|
|
|
auto derivedPubKey = mPubKey.derive(derivation.toStdString());
|
2022-07-07 20:57:06 +02:00
|
|
|
QVERIFY(derivedPubKey.isValid());
|
|
|
|
|
id = derivedPubKey.getKeyId();
|
|
|
|
|
address = CashAddress::encodeCashAddr("bitcoincash",
|
|
|
|
|
{ CashAddress::PUBKEY_TYPE,
|
|
|
|
|
std::vector<uint8_t>(id.begin(), id.end())});
|
|
|
|
|
QCOMPARE(QString::fromStdString(address), bitcoinAddress);
|
2022-07-11 13:31:24 +02:00
|
|
|
|
2024-04-29 21:19:24 +02:00
|
|
|
// method 3, use the static method: HDMasterPubkey::fromHDMaster()
|
2022-07-11 13:31:24 +02:00
|
|
|
auto mPubKey2 = HDMasterPubkey::fromHDMaster(mkey, derivation.toStdString());
|
|
|
|
|
QVERIFY(mPubKey2.isValid());
|
|
|
|
|
QCOMPARE(mPubKey2.chain(), HDMasterPubkey::MainChain);
|
2022-07-11 18:04:14 +02:00
|
|
|
QCOMPARE(mPubKey2.toString(), xpub);
|
2022-07-11 13:31:24 +02:00
|
|
|
|
|
|
|
|
derivedPubKey = mPubKey2.derive(derivation.toStdString());
|
|
|
|
|
QVERIFY(derivedPubKey.isValid());
|
|
|
|
|
id = derivedPubKey.getKeyId();
|
|
|
|
|
address = CashAddress::encodeCashAddr("bitcoincash",
|
|
|
|
|
{ CashAddress::PUBKEY_TYPE,
|
|
|
|
|
std::vector<uint8_t>(id.begin(), id.end())});
|
|
|
|
|
QCOMPARE(QString::fromStdString(address), bitcoinAddress);
|
2022-07-07 20:54:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestHDWallets::hdwallet_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<QString>("mnemonic"); // n-words of seed phrase.
|
2023-10-19 16:13:32 +03:00
|
|
|
QTest::addColumn<HDMasterKey::MnemonicType>("mnemonicType");
|
2022-07-07 20:54:57 +02:00
|
|
|
QTest::addColumn<QString>("masterPrivKey");
|
|
|
|
|
QTest::addColumn<QString>("derivation");
|
|
|
|
|
QTest::addColumn<QString>("bitcoinAddress");
|
|
|
|
|
|
|
|
|
|
QTest::newRow("small-zero")
|
|
|
|
|
<< "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::BIP39Mnemonic
|
2022-07-07 20:54:57 +02:00
|
|
|
<< "xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu"
|
|
|
|
|
<< "m/0'/0'/0" << "bitcoincash:qp8aaqjvjrujq3fk2hdwcy98pur8hsd2c5rvshznat";
|
|
|
|
|
QTest::newRow("south")
|
|
|
|
|
<< "south monkey fire corn link estate burger lucky bronze pet chapter lamp"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::BIP39Mnemonic
|
2022-07-07 20:54:57 +02:00
|
|
|
<< "xprv9s21ZrQH143K4TATPFMZLvCbsFigZdnRDvD2E9dftwgLWUcEWDmRJFi7MuKRSCUd3cFgcyzpvEfAupd3AhK5JJ8RGxDtnYH8vXjYNn1kTmR"
|
|
|
|
|
<< "m/44'/145'/0'/0/0" << "bitcoincash:qrg0jddykyfeal70xduvyeathd3ulhm7hv22m3t7na";
|
|
|
|
|
QTest::newRow("south 2")
|
|
|
|
|
<< "south monkey fire corn link estate burger lucky bronze pet chapter lamp"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::BIP39Mnemonic
|
2022-07-07 20:54:57 +02:00
|
|
|
<< "xprv9s21ZrQH143K4TATPFMZLvCbsFigZdnRDvD2E9dftwgLWUcEWDmRJFi7MuKRSCUd3cFgcyzpvEfAupd3AhK5JJ8RGxDtnYH8vXjYNn1kTmR"
|
|
|
|
|
<< "m/44'/145'/0'/0/10" << "bitcoincash:qpk93uwjdpw6aezz0sy0vpv3my4sm0ths55ydxed2n";
|
2023-10-19 16:13:32 +03:00
|
|
|
QTest::newRow("Electrum Seed (coincidentally, matches BIP39 checksum) 1")
|
|
|
|
|
<< "already later tiger purse virtual author science beyond kind common victory excuse"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::ElectrumMnemonic
|
2023-10-19 16:13:32 +03:00
|
|
|
<< "xprv9s21ZrQH143K2s87gXNhmcutDi8P359wpyrZucphFTg4y98EXiwMPfHAeVi7pzAzSNViW83U4jJmLK46mTZuyKEUw3DKv2VidLwDKFA61ha"
|
|
|
|
|
<< "m/" << "bitcoincash:qzgcz9lwpwxw8x5svzl5k828ck0lwffjlckq5rt9n2";
|
|
|
|
|
QTest::newRow("Electrum Seed (coincidentally, matches BIP39 checksum) 2")
|
|
|
|
|
<< "already later tiger purse virtual author science beyond kind common victory excuse"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::ElectrumMnemonic
|
2023-10-19 16:13:32 +03:00
|
|
|
<< "xprv9s21ZrQH143K2s87gXNhmcutDi8P359wpyrZucphFTg4y98EXiwMPfHAeVi7pzAzSNViW83U4jJmLK46mTZuyKEUw3DKv2VidLwDKFA61ha"
|
|
|
|
|
<< "m/0/0" << "bitcoincash:qrrequmjr9v6qzr760qv7rxgtucmkyvgay8rv3m9rk";
|
|
|
|
|
QTest::newRow("Electrum Seed (coincidentally, matches BIP39 checksum) 3")
|
|
|
|
|
<< "already later tiger purse virtual author science beyond kind common victory excuse"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::BIP39Mnemonic
|
2023-10-19 16:13:32 +03:00
|
|
|
<< "xprv9s21ZrQH143K2PU84C6PDoEyoHd7Km7CqGNiAG72G2hoEW5Qt2MbCTbSk4boxdDpb5VAowN6PpW9srsGgopUxeG981ugr848vTWiufDLwu3"
|
|
|
|
|
<< "m/44'/145'/0'" << "bitcoincash:qzftdqk5g9npzu2hkvuq4c8p0egw479ctvs4lmnc8d";
|
|
|
|
|
QTest::newRow("Electrum Seed (coincidentally, matches BIP39 checksum) 4")
|
|
|
|
|
<< "already later tiger purse virtual author science beyond kind common victory excuse"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::BIP39Mnemonic
|
2023-10-19 16:13:32 +03:00
|
|
|
<< "xprv9s21ZrQH143K2PU84C6PDoEyoHd7Km7CqGNiAG72G2hoEW5Qt2MbCTbSk4boxdDpb5VAowN6PpW9srsGgopUxeG981ugr848vTWiufDLwu3"
|
|
|
|
|
<< "m/44'/145'/0'/0/0" << "bitcoincash:qr2vk59jsua8j5tefa7zh097re5xexu6csl2uju6v2";
|
|
|
|
|
QTest::newRow("Electrum Seed Long")
|
|
|
|
|
<< "panel swamp swear shoulder recycle one curious pistol seed abandon heart hunt emotion spare exhibit uncle safe galaxy gather pole smart involve common other"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< HDMasterKey::ElectrumMnemonic
|
2023-10-19 16:13:32 +03:00
|
|
|
<< "xprv9s21ZrQH143K2F39PdGLYXuR7ZiGZwFZkGuJwthhqfvuyoCzANxDHSV2Jf3G1B42Pv63L9gvVNb9JNwJHZLRmhaiD71MywquNoAJDivTxad"
|
2024-04-29 21:19:24 +02:00
|
|
|
<< "m/0/0" << "bitcoincash:qrdlftxwd0q7fptapa32x3jtv3g62xnytglwec2yxg";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestHDWallets::xpriv()
|
|
|
|
|
{
|
|
|
|
|
// create a hdmasterkey based on seed.
|
|
|
|
|
// test if xpriv is Ok
|
|
|
|
|
// derive to something deep and test if Ok
|
|
|
|
|
// import and check the public key version is Ok
|
|
|
|
|
|
|
|
|
|
// then derive the imported xpriv and xpubs to check we use depth properly.
|
|
|
|
|
HDMasterKey key1 = HDMasterKey::fromMnemonic("south monkey fire corn link estate burger lucky bronze pet chapter lamp", HDMasterKey::BIP39Mnemonic);
|
|
|
|
|
QCOMPARE(key1.toXPrivString("m/"), "xprv9s21ZrQH143K4TATPFMZLvCbsFigZdnRDvD2E9dftwgLWUcEWDmRJFi7MuKRSCUd3cFgcyzpvEfAupd3AhK5JJ8RGxDtnYH8vXjYNn1kTmR");
|
|
|
|
|
QCOMPARE(key1.toXPrivString("m/44'/0'/0"), "xprv9yxnfQHwCeGNQCiYrYrVYQ6XMMKjFaA7kDPctdwNyGHNLmZ8yiy6M8SaNnza7W1N6efbXEM9dK6TaiPSdfR69b5n5Zgd72Y8YQnzuD7Xqc9");
|
|
|
|
|
|
|
|
|
|
auto deepPrv = key1.toXPrivString("m/44'/145'/0'");
|
|
|
|
|
HDMasterKey::Chain chain;
|
|
|
|
|
HDMasterKey key2 = HDMasterKey::fromXPriv(deepPrv, &chain);
|
|
|
|
|
QVERIFY(key2.isValid());
|
|
|
|
|
QCOMPARE(key2.level(), 3);
|
|
|
|
|
QCOMPARE(chain, HDMasterKey::MainChain);
|
|
|
|
|
|
|
|
|
|
auto pk = key2.derive("m/44'/145'/0'/0/10");
|
|
|
|
|
QVERIFY(pk.isValid());
|
|
|
|
|
auto id = pk.getPubKey().getKeyId();
|
|
|
|
|
auto address = CashAddress::encodeCashAddr("bitcoincash",
|
|
|
|
|
{ CashAddress::PUBKEY_TYPE, std::vector<uint8_t>(id.begin(), id.end())});
|
|
|
|
|
QCOMPARE(address, "bitcoincash:qpk93uwjdpw6aezz0sy0vpv3my4sm0ths55ydxed2n");
|
2022-07-07 20:54:57 +02:00
|
|
|
}
|