Files
thehub/testing/utils/TestHDWallets.cpp
T
tomFlowee efa6c05e1a A API review of CashAddr.h
This removes from the header all private methods, adds API docs and does
some renames that make code using this API much more readable.
2026-02-09 15:28:04 +01:00

185 lines
9.2 KiB
C++

/*
* 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>
#include <utils/HDMasterPubkey.h>
#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);
QFETCH(HDMasterKey::MnemonicType, mnemonicType);
QFETCH(QString, masterPrivKey);
QFETCH(QString, derivation);
QFETCH(QString, bitcoinAddress);
HDMasterKey mkey = HDMasterKey::fromMnemonic(mnemonic.toStdString(), mnemonicType);
QVERIFY(mkey.isValid());
QCOMPARE(QString::fromStdString(mkey.toXPrivString("m/")), masterPrivKey);
// second way of creation should give same result.
HDMasterKey mkey2 = HDMasterKey::fromXPriv(masterPrivKey.toStdString());
QVERIFY(mkey2.isValid());
QVERIFY(mkey2 == mkey);
auto derivedKey = mkey.derive(derivation.toStdString());
QVERIFY(derivedKey.isValid());
auto id = derivedKey.getPubKey().getKeyId();
auto address = CashAddress::encode("bitcoincash",
{ CashAddress::PubkeyType,
std::vector<uint8_t>(id.begin(), id.end())});
QCOMPARE(QString::fromStdString(address), bitcoinAddress);
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
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());
}
const auto xpub = mkey.toXPubString(hardDerivPath, HDMasterKey::MainChain);
auto mPubKey = HDMasterPubkey::fromXPub(xpub);
QVERIFY(mPubKey.isValid());
QCOMPARE(mPubKey.chain(), HDMasterPubkey::MainChain);
QCOMPARE(mPubKey.toString(), xpub);
auto derivedPubKey = mPubKey.derive(derivation.toStdString());
QVERIFY(derivedPubKey.isValid());
id = derivedPubKey.getKeyId();
address = CashAddress::encode("bitcoincash",
{ CashAddress::PubkeyType,
std::vector<uint8_t>(id.begin(), id.end())});
QCOMPARE(QString::fromStdString(address), bitcoinAddress);
// method 3, use the static method: HDMasterPubkey::fromHDMaster()
auto mPubKey2 = HDMasterPubkey::fromHDMaster(mkey, derivation.toStdString());
QVERIFY(mPubKey2.isValid());
QCOMPARE(mPubKey2.chain(), HDMasterPubkey::MainChain);
QCOMPARE(mPubKey2.toString(), xpub);
derivedPubKey = mPubKey2.derive(derivation.toStdString());
QVERIFY(derivedPubKey.isValid());
id = derivedPubKey.getKeyId();
address = CashAddress::encode("bitcoincash",
{ CashAddress::PubkeyType,
std::vector<uint8_t>(id.begin(), id.end())});
QCOMPARE(QString::fromStdString(address), bitcoinAddress);
}
void TestHDWallets::hdwallet_data()
{
QTest::addColumn<QString>("mnemonic"); // n-words of seed phrase.
QTest::addColumn<HDMasterKey::MnemonicType>("mnemonicType");
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"
<< HDMasterKey::BIP39Mnemonic
<< "xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu"
<< "m/0'/0'/0" << "bitcoincash:qp8aaqjvjrujq3fk2hdwcy98pur8hsd2c5rvshznat";
QTest::newRow("south")
<< "south monkey fire corn link estate burger lucky bronze pet chapter lamp"
<< HDMasterKey::BIP39Mnemonic
<< "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"
<< HDMasterKey::BIP39Mnemonic
<< "xprv9s21ZrQH143K4TATPFMZLvCbsFigZdnRDvD2E9dftwgLWUcEWDmRJFi7MuKRSCUd3cFgcyzpvEfAupd3AhK5JJ8RGxDtnYH8vXjYNn1kTmR"
<< "m/44'/145'/0'/0/10" << "bitcoincash:qpk93uwjdpw6aezz0sy0vpv3my4sm0ths55ydxed2n";
QTest::newRow("Electrum Seed (coincidentally, matches BIP39 checksum) 1")
<< "already later tiger purse virtual author science beyond kind common victory excuse"
<< HDMasterKey::ElectrumMnemonic
<< "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"
<< HDMasterKey::ElectrumMnemonic
<< "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"
<< HDMasterKey::BIP39Mnemonic
<< "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"
<< HDMasterKey::BIP39Mnemonic
<< "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"
<< HDMasterKey::ElectrumMnemonic
<< "xprv9s21ZrQH143K2F39PdGLYXuR7ZiGZwFZkGuJwthhqfvuyoCzANxDHSV2Jf3G1B42Pv63L9gvVNb9JNwJHZLRmhaiD71MywquNoAJDivTxad"
<< "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::encode("bitcoincash",
{ CashAddress::PubkeyType, std::vector<uint8_t>(id.begin(), id.end())});
QCOMPARE(address, "bitcoincash:qpk93uwjdpw6aezz0sy0vpv3my4sm0ths55ydxed2n");
}