From 7cb123b8b8c41487dcc3b02f0449634594475faf Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Fri, 18 Feb 2022 18:41:47 +0100 Subject: [PATCH 01/24] Bech32M encoding --- core/src/bitcoin/bech32/BTCBech32.cpp | 63 ++++- core/src/bitcoin/bech32/BTCBech32.h | 9 +- .../CosmosLikeBech32ParametersHelpers.hpp | 12 +- core/src/math/bech32/Bech32.cpp | 34 ++- core/src/math/bech32/Bech32.h | 44 ++-- core/src/math/bech32/Bech32Factory.cpp | 13 +- core/src/math/bech32/Bech32Parameters.cpp | 22 +- core/src/math/bech32/Bech32Parameters.h | 18 +- core/test/math/bech32_test.cpp | 221 ++++++++++++++++++ 9 files changed, 364 insertions(+), 72 deletions(-) create mode 100644 core/test/math/bech32_test.cpp diff --git a/core/src/bitcoin/bech32/BTCBech32.cpp b/core/src/bitcoin/bech32/BTCBech32.cpp index c2c46901eb..46d73252e7 100644 --- a/core/src/bitcoin/bech32/BTCBech32.cpp +++ b/core/src/bitcoin/bech32/BTCBech32.cpp @@ -28,9 +28,9 @@ * */ - #include "BTCBech32.h" #include +#include namespace ledger { namespace core { @@ -68,30 +68,69 @@ namespace ledger { std::vector converted; converted.insert(converted.end(), version.begin(), version.end()); Bech32::convertBits(data, fromBits, toBits, pad, converted); - return encodeBech32(converted); + if (version.size() == 1) { + if (version == _bech32Params.P2WPKHVersion || version == _bech32Params.P2WSHVersion) { + return encodeBech32(converted, 1); + } else if (version[0] <= 16) { + return encodeBech32(converted, 0x2bc830a3); + } + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid Bech32 version value : must be in the range 0..16 inclusive"); + } + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid Bech32 version length : must be exactly 1 byte"); } std::pair, std::vector> BTCBech32::decode(const std::string& str) const { - auto decoded = decodeBech32(str); - if (decoded.first != _bech32Params.hrp || decoded.second.size() < 1) { - throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : Invalid bech 32 format"); + auto decoded = decodeBech32Raw(str); + + if (decoded.second.size() < 1) { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : invalid Bech32 format"); + } + + std::vector version{decoded.second[0]}; + + if (decoded.second.size() < _bech32Params.checksumSize) { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : the address is less than checksum size"); + } + + if (version[0] == 0) { + if (!verifyChecksum(decoded.second, 1)) { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Bech32 checksum verification failed"); + } + } else if (version[0] <= 16) { + if (!verifyChecksum(decoded.second, 0x2bc830a3)) { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Bech32M checksum verification failed"); + } + } else { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid Bech32 version value : must be in the range 0..16 inclusive"); + } + + // strip the checksum + std::vector decoded_address(decoded.second.begin(), decoded.second.end() - _bech32Params.checksumSize); + + if (decoded.first != _bech32Params.hrp) { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : Invalid Bech32 hrp"); } std::vector converted; int fromBits = 5, toBits = 8; bool pad = false; - auto result = Bech32::convertBits(std::vector(decoded.second.begin() + 1, decoded.second.end()), + auto result = Bech32::convertBits(std::vector(decoded_address.begin() + 1, decoded_address.end()), fromBits, toBits, pad, converted); - if (!result || converted.size() < 2 || - converted.size() > 40 || decoded.second[0] > 16 || - (decoded.second[0] == 0 && converted.size() != 20 && converted.size() != 32)) { - throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : Invalid bech 32 format"); + + if (!result || converted.size() < 2 || converted.size() > 40 || version.size() != 1) { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : Invalid Bech32 format"); } - std::vector version{decoded.second[0]}; - return std::make_pair(version, converted); + + if ((converted.size() == 20 && version == _bech32Params.P2WPKHVersion) || + (converted.size() == 32 && (version == _bech32Params.P2WSHVersion + || version == _bech32Params.P2TRVersion))) { + return std::make_pair(version, converted); + } + + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : Invalid Bech32 format, data length and version missmatch"); } } } diff --git a/core/src/bitcoin/bech32/BTCBech32.h b/core/src/bitcoin/bech32/BTCBech32.h index fcbf5343fa..cb9296ba8d 100644 --- a/core/src/bitcoin/bech32/BTCBech32.h +++ b/core/src/bitcoin/bech32/BTCBech32.h @@ -42,15 +42,16 @@ namespace ledger { BTCBech32(const std::string &networkIdentifier) : Bech32(Bech32Parameters::getBech32Params(networkIdentifier)){ }; - uint64_t polymod(const std::vector& values) const override; - - std::vector expandHrp(const std::string& hrp) const override; - std::string encode(const std::vector& hash, const std::vector& version) const override; std::pair, std::vector> decode(const std::string& str) const override; + + protected: + uint64_t polymod(const std::vector& values) const override; + + std::vector expandHrp(const std::string& hrp) const override; }; } } diff --git a/core/src/cosmos/bech32/CosmosLikeBech32ParametersHelpers.hpp b/core/src/cosmos/bech32/CosmosLikeBech32ParametersHelpers.hpp index d7421e08b7..108c9e2c9c 100644 --- a/core/src/cosmos/bech32/CosmosLikeBech32ParametersHelpers.hpp +++ b/core/src/cosmos/bech32/CosmosLikeBech32ParametersHelpers.hpp @@ -51,7 +51,8 @@ static const Bech32Parameters::Bech32Struct getBech32Params(api::CosmosBech32Typ 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x00}, - {0x00}}; + {0x00}, + {0xFF}}; static const Bech32Parameters::Bech32Struct COSMOS_PUB_VAL = { "cosmosvaloperpub", "cosmosvaloperpub", @@ -59,7 +60,8 @@ static const Bech32Parameters::Bech32Struct getBech32Params(api::CosmosBech32Typ 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x00}, - {0x00}}; + {0x00}, + {0xFF}}; static const Bech32Parameters::Bech32Struct COSMOS = { "cosmos", "cosmos", @@ -67,7 +69,8 @@ static const Bech32Parameters::Bech32Struct getBech32Params(api::CosmosBech32Typ 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x01}, - {0x01}}; + {0x01}, + {0xFF}}; static const Bech32Parameters::Bech32Struct COSMOS_VAL = { "cosmosvaloper", "cosmosvaloper", @@ -75,7 +78,8 @@ static const Bech32Parameters::Bech32Struct getBech32Params(api::CosmosBech32Typ 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x01}, - {0x01}}; + {0x01}, + {0xFF}}; switch (type) { case api::CosmosBech32Type::PUBLIC_KEY: return COSMOS_PUB; diff --git a/core/src/math/bech32/Bech32.cpp b/core/src/math/bech32/Bech32.cpp index a633ee3985..8393790209 100644 --- a/core/src/math/bech32/Bech32.cpp +++ b/core/src/math/bech32/Bech32.cpp @@ -28,9 +28,9 @@ * */ - #include "Bech32.h" #include + namespace ledger { namespace core { @@ -50,15 +50,15 @@ namespace ledger { }; // Verify a checksum. - bool Bech32::verifyChecksum(const std::vector& values) const { - return polymod(vector::concat(expandHrp(_bech32Params.hrp), values)) == 1; + bool Bech32::verifyChecksum(const std::vector& values, uint32_t bech32_param) const { + return polymod(vector::concat(expandHrp(_bech32Params.hrp), values)) == bech32_param; } // Create a checksum. - std::vector Bech32::createChecksum(const std::vector& values) const { + std::vector Bech32::createChecksum(const std::vector& values, uint32_t bech32_param) const { std::vector enc = vector::concat(expandHrp(_bech32Params.hrp), values); enc.resize(enc.size() + _bech32Params.checksumSize); - uint64_t mod = polymod(enc) ^ 1; + uint64_t mod = polymod(enc) ^ bech32_param; std::vector ret; ret.resize(_bech32Params.checksumSize); // Can't use ssize_t because it's posix specific (problem with MSVC build) so let's @@ -70,9 +70,9 @@ namespace ledger { return ret; } - std::string Bech32::encodeBech32(const std::vector& values) const { + std::string Bech32::encodeBech32(const std::vector& values, uint32_t bech32_param) const { // Values here should be concatenation of version (base256) + hash (base32) - std::vector checksum = createChecksum(values); + std::vector checksum = createChecksum(values, bech32_param); std::vector combined = vector::concat(values, checksum); std::string ret = _bech32Params.hrp + _bech32Params.separator; ret.reserve(ret.size() + combined.size()); @@ -85,8 +85,8 @@ namespace ledger { return ret; } - std::pair> - Bech32::decodeBech32(const std::string& str) const { + // Caller is responsible for checksum verification. + std::pair> Bech32::decodeBech32Raw(const std::string& str) const { bool lower = false, upper = false; bool ok = true; for (size_t i = 0; ok && i < str.size(); ++i) { @@ -110,14 +110,24 @@ namespace ledger { for (size_t i = 0; i < pos; ++i) { hrp += toLowerCase(str[i]); } - if (verifyChecksum(values)) { - return std::make_pair(hrp, std::vector(values.begin(), values.end() - _bech32Params.checksumSize)); - } + return std::make_pair(hrp, values); } } return std::make_pair(std::string(), std::vector()); } + // Decodes and verifies checksum according to BECH32 rules. + std::pair> Bech32::decodeBech32(const std::string& str) const { + const std::pair> decoded = decodeBech32Raw(str); + const std::string& hrp = decoded.first; + const std::vector& values = decoded.second; + + if (verifyChecksum(values, 1)) { + return std::make_pair(hrp, std::vector(values.begin(), values.end() - _bech32Params.checksumSize)); + } + return std::make_pair(std::string(), std::vector()); + } + // Convert to lower case. unsigned char Bech32::toLowerCase(unsigned char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; diff --git a/core/src/math/bech32/Bech32.h b/core/src/math/bech32/Bech32.h index 44ea2e7174..38efb7f041 100644 --- a/core/src/math/bech32/Bech32.h +++ b/core/src/math/bech32/Bech32.h @@ -46,27 +46,39 @@ namespace ledger { class Bech32 { public: Bech32(Bech32Parameters::Bech32Struct bech32Params) : _bech32Params(bech32Params) {} - // Find the polynomial with value coefficients mod the generator as 64-bit. - virtual uint64_t polymod(const std::vector& values) const = 0; - - // Expand a HRP for use in checksum computation. - virtual std::vector expandHrp(const std::string& hrp) const = 0; - - bool verifyChecksum(const std::vector& values) const; - - std::vector createChecksum(const std::vector& values) const; virtual std::string encode(const std::vector& hash, const std::vector& version) const = 0; - // Decode from bech32 address - // @return pair - std::pair> - decodeBech32(const std::string& str) const; // @return tuple virtual std::pair, std::vector> decode(const std::string& str) const = 0; + Bech32Parameters::Bech32Struct getBech32Params() const { + return _bech32Params; + } + + protected: + std::string encodeBech32(const std::vector& values, uint32_t bech32_param=1) const; + + // Decode from bech32 address and verify checksum + // @return pair + std::pair> decodeBech32(const std::string& str) const; + + // Decode from bech32 address and doesn't verify checksum + // @return pair + std::pair> decodeBech32Raw(const std::string& str) const; + + // Find the polynomial with value coefficients mod the generator as 64-bit. + virtual uint64_t polymod(const std::vector& values) const = 0; + + // Expand a HRP for use in checksum computation. + virtual std::vector expandHrp(const std::string& hrp) const = 0; + + bool verifyChecksum(const std::vector& values, uint32_t bech32_param) const; + + std::vector createChecksum(const std::vector& values, uint32_t bech32_param) const; + static unsigned char toLowerCase(unsigned char c); static bool convertBits(const std::vector& in, @@ -75,12 +87,6 @@ namespace ledger { bool pad, std::vector& out); - Bech32Parameters::Bech32Struct getBech32Params() const { - return _bech32Params; - } - - protected: - std::string encodeBech32(const std::vector& values) const; Bech32Parameters::Bech32Struct _bech32Params; }; } diff --git a/core/src/math/bech32/Bech32Factory.cpp b/core/src/math/bech32/Bech32Factory.cpp index 299d306b7d..6310980b25 100644 --- a/core/src/math/bech32/Bech32Factory.cpp +++ b/core/src/math/bech32/Bech32Factory.cpp @@ -38,12 +38,12 @@ namespace ledger { namespace core { Option> Bech32Factory::newBech32Instance(const std::string &networkIdentifier) { const auto btcBech32Identifiers = std::vector{"btc", "btc_testnet", "btc_regtest", "dgb", "ltc"}; - const auto cosmosBech32Identifiers = std::vector{ - api::to_string(api::CosmosBech32Type::ADDRESS), - api::to_string(api::CosmosBech32Type::ADDRESS_VAL), - api::to_string(api::CosmosBech32Type::PUBLIC_KEY), - api::to_string(api::CosmosBech32Type::PUBLIC_KEY_VAL) - }; + const auto cosmosBech32Identifiers = std::vector { + api::to_string(api::CosmosBech32Type::ADDRESS), + api::to_string(api::CosmosBech32Type::ADDRESS_VAL), + api::to_string(api::CosmosBech32Type::PUBLIC_KEY), + api::to_string(api::CosmosBech32Type::PUBLIC_KEY_VAL) + }; if (std::find(btcBech32Identifiers.begin(), btcBech32Identifiers.end(), networkIdentifier) != btcBech32Identifiers.end()) { return Option>(std::make_shared(networkIdentifier)); } else if (networkIdentifier == "abc") { @@ -67,3 +67,4 @@ namespace ledger { } } } + diff --git a/core/src/math/bech32/Bech32Parameters.cpp b/core/src/math/bech32/Bech32Parameters.cpp index 8b76c8b287..ed6c74a10d 100644 --- a/core/src/math/bech32/Bech32Parameters.cpp +++ b/core/src/math/bech32/Bech32Parameters.cpp @@ -35,6 +35,7 @@ #include #include #include + using namespace soci; namespace ledger { namespace core { @@ -48,7 +49,8 @@ namespace ledger { 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x00}, - {0x00} + {0x00}, + {0x01} }; return BITCOIN; } else if (networkIdentifier == "btc_testnet") { @@ -59,7 +61,8 @@ namespace ledger { 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x00}, - {0x00} + {0x00}, + {0x01} }; return BITCOIN_TESTNET; } else if (networkIdentifier == "btc_regtest") { @@ -70,7 +73,8 @@ namespace ledger { 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x00}, - {0x00} + {0x00}, + {0x01} }; return BITCOIN_REGTEST; } else if (networkIdentifier == "abc") { @@ -81,7 +85,8 @@ namespace ledger { 8, {0x98f2bc8e61ULL, 0x79b76d99e2ULL, 0xf33e5fb3c4ULL, 0xae2eabe2a8ULL, 0x1e4f43e470ULL}, {0x00}, - {0x08} + {0x08}, + {0xFF} }; return BITCOIN_CASH; } else if (networkIdentifier == "dgb") { @@ -93,7 +98,8 @@ namespace ledger { 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x00}, - {0x00} + {0x00}, + {0xFF} }; return DIGIBYTE; } else if (networkIdentifier == "ltc") { @@ -105,7 +111,8 @@ namespace ledger { 6, {0x3b6a57b2ULL, 0x26508e6dULL, 0x1ea119faULL, 0x3d4233ddULL, 0x2a1462b3ULL}, {0x00}, - {0x00} + {0x00}, + {0xFF} }; return LITECOIN; } @@ -153,4 +160,5 @@ namespace ledger { } } } -} \ No newline at end of file +} + diff --git a/core/src/math/bech32/Bech32Parameters.h b/core/src/math/bech32/Bech32Parameters.h index fbc01a92a1..9ceecb017e 100644 --- a/core/src/math/bech32/Bech32Parameters.h +++ b/core/src/math/bech32/Bech32Parameters.h @@ -55,6 +55,7 @@ namespace ledger { std::vector generator; std::vector P2WPKHVersion; std::vector P2WSHVersion; + std::vector P2TRVersion; Bech32Struct() = default; Bech32Struct(const std::string &_name, @@ -63,14 +64,15 @@ namespace ledger { size_t _checksumSize, const std::vector &_generator, const std::vector &_P2WPKHVersion, - const std::vector &_P2WSHVersion) : name(_name), - hrp(_hrp), - separator(_separator), - checksumSize(_checksumSize), - generator(_generator), - P2WPKHVersion(_P2WPKHVersion), - P2WSHVersion(_P2WSHVersion) - + const std::vector &_P2WSHVersion, + const std::vector &_P2TRVersion) : name(_name), + hrp(_hrp), + separator(_separator), + checksumSize(_checksumSize), + generator(_generator), + P2WPKHVersion(_P2WPKHVersion), + P2WSHVersion(_P2WSHVersion), + P2TRVersion(_P2TRVersion) {}; }; diff --git a/core/test/math/bech32_test.cpp b/core/test/math/bech32_test.cpp new file mode 100644 index 0000000000..b2b4b891f7 --- /dev/null +++ b/core/test/math/bech32_test.cpp @@ -0,0 +1,221 @@ +/* + * + * bech32_test + * ledger-core + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Ledger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include + +using namespace ledger::core; + +struct Bech32_TestCase { + std::string _network_id; + std::string _address; + uint8_t _version; + std::string _scriptPubKey; + // scriptPubKey has following format: + // - witness version (1 byte, possible values: 0..16 inclusive) + // -- 0 - segwit address (P2WPKH, P2WSH) + // -- 1 - taproot address (P2TR) + // - key hash or key (P2TR): + // -- key hash length (1 byte, posible values: for witness version 0: 20 or 32, see BIP 141) + // -- key hash (for witness version 0: 20 or 32 bytes) + inline std::string getPubKeyHash() const { + if (_scriptPubKey.size() < 6) + throw std::logic_error(std::string("Bech32_TestCase._scriptPubKey is too short: ") + _scriptPubKey); + // The length of hex encoded pubKeyHash must include at least: + // version: exactly 2 chars + // length: exactly 2 chars + // value: at least 2 chars + return _scriptPubKey.substr(4); // skip version and length + } +}; + +std::string toLower(std::string s) { + std::string l; + std::transform(s.begin(), s.end(), std::back_inserter(l), [](unsigned char c){ return std::tolower(c); }); + return l; +} + +std::vector bech32_segwit_testcases = { + // Official test vectors + {"btc", "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", // BIP 173 & 350 + 0x00, "0014751e76e8199196d454941c45d1b3a323f1433bd6"}, + {"btc", "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", + 0x00, "0014751e76e8199196d454941c45d1b3a323f1433bd6"}, + {"btc_testnet", "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", // BIP 173 & 350 + 0x00, "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + {"btc_testnet", "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", // BIP 173 & 350 + 0x00, "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"}, + + {"btc", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", // BIP 350 + 0x01, "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}, + {"btc_testnet", "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", // BIP 350 + 0x01, "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + + // Source: https://github.com/bitcoin/bitcoin/blob/master/test/functional/test_framework/segwit_addr.py + // P2WPKH + {"btc_regtest", "bcrt1qthmht0k2qnh3wy7336z05lu2km7emzfpm3wg46", + 0x00, "00145df775beca04ef1713d18e84fa7f8ab6fd9d8921"}, + // P2WSH + {"btc_regtest", "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj", + 0x00, "00200000000000000000000000000000000000000000000000000000000000000000"}, + {"btc_regtest", "bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85", + 0x00, "00204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260"}, + // P2TR + {"btc_regtest", "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6", + 0x01, "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}, + + // Bitcoin cash + // Source: https://github.com/LedgerHQ/ledger-live-common/blob/master/src/__tests__/families/bitcoin/wallet-btc/utils.test.ts + {"abc", "bitcoincash:qqmyc72pkyx8c0ppgeuummq6clzverhxnsk3qh6jcf", + 0x00, "0014364c7941b10c7c3c214679cdec1ac7c4cc8ee69c"}, + {"abc", "bitcoincash:qzl0x0982hy9xrh99wdnejx4eecdn02jv58as5p595", + 0x00, "0014bef33ca755c8530ee52b9b3cc8d5ce70d9bd5265"}, + + // Litecoin + {"ltc", "ltc1q3e4eh3lldvx97zg6d74x4ns6v5a4j4hwwqycwv", + 0x00, "00148e6b9bc7ff6b0c5f091a6faa6ace1a653b5956ee"}, + + // Digibyte + {"dgb", "dgb1q7zjgqa23xzf602ljfrc94248a9u27xml08nhct", + 0x00, "0014f0a48075513093a7abf248f05aaaa7e978af1b7f"} +}; + +std::vector bech32_invalid_addresses = { + // Official test vectors from BIP 173 + "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", // Invalid human-readable part + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", // Invalid checksum + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", // Invalid witness version + "bc1rw5uspcuh", // Invalid program length + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", // Invalid program length + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", // Invalid program length for witness version 0 (per BIP141) + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", // Mixed case + "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", // zero padding of more than 4 bits + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", // Non-zero padding in 8-to-5 conversion + "bc1gmk9yu", // Empty data section + + // Source: https://github.com/LedgerHQ/ledger-live-common/blob/master/src/__tests__/families/bitcoin/wallet-btc/utils.test.ts + "bitcoincash:qqmyc72pkyx8c0ppgeuummq6clzverhxnsk3qh6jc1", + "bitcoincash:qzl0x0982hy9xrh99wdnejx4eecdn02jv58as5p599", + "ltc1q3e4eh3lldvx97zg6d74x4ns6v5a4j4hwwqycww", + "dgb1q7zjgqa23xzf602ljfrc94248a9u27xml08nhcc", + + // Addresses below are correctly encoded but aren't accepted + // because their meaning isn't clear + "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", // Unknown witness versions + // witness version: 0x02, decoded: "5210751e76e8199196d454941c45d1b3a323" + + "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", // Data are 40 bytes long (only 32 bytes for P2TR) + // witness version: 0x01, + // decoded: "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6" + + "BC1SW50QGDZ25J", // Unknown witness version and decoded data too short + // witness version: 0x10, decoded: "6002751e" +}; + +TEST(Bech32, EncodeAddress) { + for (const Bech32_TestCase& testcase : bech32_segwit_testcases) { + Option> opt_bech32 = Bech32Factory::newBech32Instance(testcase._network_id); + EXPECT_TRUE(opt_bech32.hasValue()); + std::shared_ptr sp_bech32 = opt_bech32.getValue(); + EXPECT_TRUE(sp_bech32.get() != nullptr); + + const std::string address = sp_bech32->encode(hex::toByteArray(testcase.getPubKeyHash()), {testcase._version}); + std::cerr << "Encoded: " << testcase._scriptPubKey << " -> " << address << std::endl; + EXPECT_EQ(toLower(testcase._address), address); // address is encoded correctly (Bech32 is case-insensitive) + } + + std::cout << std::endl; +} + +TEST(Bech32, DecodeValidAddress) { + for (const Bech32_TestCase& testcase : bech32_segwit_testcases) { + Option> opt_bech32 = Bech32Factory::newBech32Instance(testcase._network_id); + EXPECT_TRUE(opt_bech32.hasValue()); + std::shared_ptr sp_bech32 = opt_bech32.getValue(); + EXPECT_TRUE(sp_bech32.get() != nullptr); + + std::pair, std::vector> decoded; + try { + decoded = sp_bech32->decode(testcase._address); + } catch (const std::exception& e) { + FAIL() << e.what() << std::endl + << "Input address is: \"" << testcase._address + << "\"" << std::endl; + } catch (...) { + FAIL() << "An error happened while decoding address \"" + << testcase._address << "\"" << std::endl; + } + + std::cerr << testcase._network_id << " " + << hex::toString(decoded.first) << " " + << hex::toString(decoded.second) << std::endl; + + EXPECT_EQ(decoded.first.size(), 1); // version takes one byte + EXPECT_EQ(decoded.first[0], testcase._version); // version is decoded correctly + EXPECT_EQ(hex::toString(decoded.second).size(), testcase.getPubKeyHash().size()); // pubKeyHash has a correct size + EXPECT_EQ(hex::toString(decoded.second), testcase.getPubKeyHash()); // pubKeyHash is decoded correctly + + std::cerr << "Decoded: " << testcase._address << " -> " + << hex::toString(decoded.first) << " " << hex::toString(decoded.second) << std::endl; + } +} + +TEST(Bech32, DecodeInvalidAddresses) { + const std::vector netIds = {"btc", "btc_testnet", "btc_regtest", "abc", "dgb", "ltc"}; + for (const std::string& addr : bech32_invalid_addresses) { + for (const std::string& net_id : netIds) { + Option> opt_bech32 = Bech32Factory::newBech32Instance(net_id); + EXPECT_TRUE(opt_bech32.hasValue()); + std::shared_ptr sp_bech32 = opt_bech32.getValue(); + EXPECT_TRUE(sp_bech32.get() != nullptr); + + try { + std::cerr << "Trying to decode " << addr << " for network " << net_id << " "; + std::pair, std::vector> decoded + = sp_bech32->decode(addr); + } catch (const Exception& e) { + EXPECT_EQ(e.getErrorCode(), api::ErrorCode::INVALID_BECH32_FORMAT); + std::cerr << "got " << e.getMessage() << std::endl; + continue; + } catch (...) { + FAIL() << "Incorrect type of exception thrown for this address / network id combination." << std::endl + << "\taddress: " << addr << std::endl + << "\tnetwork id: " << net_id << std::endl; + } + FAIL() << "Bech32::decode() must throw an exception for this address / network id combination." << std::endl + << "\taddress: " << addr << std::endl + << "\tnetwork id: " << net_id << std::endl; + } + } +} + From 1d64c68df4f171af27003aabd26683003a02e7fe Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Mon, 21 Feb 2022 09:16:52 +0100 Subject: [PATCH 02/24] Add Bech32 tests to CMake --- core/test/math/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/test/math/CMakeLists.txt b/core/test/math/CMakeLists.txt index ccf3e3ba4b..0b1d89e06e 100644 --- a/core/test/math/CMakeLists.txt +++ b/core/test/math/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(ledger-core-math-tests bigint_tests.cpp bigint_api_tests.cpp base58_test.cpp + bech32_test.cpp fibonacci_test.cpp base_converter_tests.cpp) @@ -26,4 +27,5 @@ else() endif() include(GoogleTest) -gtest_discover_tests(ledger-core-math-tests PROPERTIES "LABELS;unit") \ No newline at end of file +gtest_discover_tests(ledger-core-math-tests PROPERTIES "LABELS;unit") + From 84b24fa2fc395d262f0df80d53e38b0a167b80e3 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:24:04 +0100 Subject: [PATCH 03/24] P2TR address support --- core/idl/bitcoin/addresses.djinni | 3 + core/idl/wallet/configuration.djinni | 1 + core/src/api/BitcoinLikeAddress.hpp | 6 ++ core/src/api/KeychainEngines.cpp | 2 + core/src/api/KeychainEngines.hpp | 2 + core/src/bitcoin/BitcoinLikeAddress.cpp | 98 ++++++++++++++----- core/src/bitcoin/BitcoinLikeAddress.hpp | 12 ++- core/src/jni/jni/BitcoinLikeAddress.cpp | 10 ++ .../bitcoin/keychains/BitcoinLikeKeychain.cpp | 8 +- .../keychains/CommonBitcoinLikeKeychains.cpp | 4 + .../keychains/CommonBitcoinLikeKeychains.hpp | 2 + .../BitcoinLikeTransactionBuilder.cpp | 2 + core/test/bitcoin/address_test.cpp | 44 ++++++--- .../keychains/keychain_test_helper.h | 23 +++-- 14 files changed, 162 insertions(+), 55 deletions(-) diff --git a/core/idl/bitcoin/addresses.djinni b/core/idl/bitcoin/addresses.djinni index 0ac5522430..be0e28a944 100644 --- a/core/idl/bitcoin/addresses.djinni +++ b/core/idl/bitcoin/addresses.djinni @@ -75,6 +75,9 @@ BitcoinLikeAddress = interface +c { # Checks if the given address is a P2WPKH address # @return True if the keychain engine is P2WPKH isP2WPKH(): bool; + # Checks if the given address is a P2TR address + # @return True if the keychain engine is P2TR + isP2TR(): bool; } # The xPUB definition for Bitcoin. diff --git a/core/idl/wallet/configuration.djinni b/core/idl/wallet/configuration.djinni index 4b1eed31ad..bb35c94f9d 100644 --- a/core/idl/wallet/configuration.djinni +++ b/core/idl/wallet/configuration.djinni @@ -29,6 +29,7 @@ KeychainEngines = interface +c { const BIP49_P2SH: string = "BIP49_P2SH"; const BIP173_P2WPKH: string = "BIP173_P2WPKH"; const BIP173_P2WSH: string = "BIP173_P2WSH"; + const BIP350_P2TR: string = "BIP350_P2TR"; } # Class of constants representing the engines to rely on for synchronization. diff --git a/core/src/api/BitcoinLikeAddress.hpp b/core/src/api/BitcoinLikeAddress.hpp index 55b8bcc19e..21eefd74c2 100644 --- a/core/src/api/BitcoinLikeAddress.hpp +++ b/core/src/api/BitcoinLikeAddress.hpp @@ -80,6 +80,12 @@ class LIBCORE_EXPORT BitcoinLikeAddress { * @return True if the keychain engine is P2WPKH */ virtual bool isP2WPKH() = 0; + + /** + * Checks if the given address is a P2TR address + * @return True if the keychain engine is P2TR + */ + virtual bool isP2TR() = 0; }; } } } // namespace ledger::core::api diff --git a/core/src/api/KeychainEngines.cpp b/core/src/api/KeychainEngines.cpp index 1b762f3356..b8110000c5 100644 --- a/core/src/api/KeychainEngines.cpp +++ b/core/src/api/KeychainEngines.cpp @@ -13,4 +13,6 @@ std::string const KeychainEngines::BIP173_P2WPKH = {"BIP173_P2WPKH"}; std::string const KeychainEngines::BIP173_P2WSH = {"BIP173_P2WSH"}; +std::string const KeychainEngines::BIP350_P2TR = {"BIP350_P2TR"}; + } } } // namespace ledger::core::api diff --git a/core/src/api/KeychainEngines.hpp b/core/src/api/KeychainEngines.hpp index 51aef5ba93..9029023fbe 100644 --- a/core/src/api/KeychainEngines.hpp +++ b/core/src/api/KeychainEngines.hpp @@ -27,6 +27,8 @@ class LIBCORE_EXPORT KeychainEngines { static std::string const BIP173_P2WPKH; static std::string const BIP173_P2WSH; + + static std::string const BIP350_P2TR; }; } } } // namespace ledger::core::api diff --git a/core/src/bitcoin/BitcoinLikeAddress.cpp b/core/src/bitcoin/BitcoinLikeAddress.cpp index 6ae27ef2a8..c99534b96c 100644 --- a/core/src/bitcoin/BitcoinLikeAddress.cpp +++ b/core/src/bitcoin/BitcoinLikeAddress.cpp @@ -62,8 +62,8 @@ namespace ledger { return getVersionFromKeychainEngine(_keychainEngine, _params); } - std::vector BitcoinLikeAddress::getVersionFromKeychainEngine(const std::string &keychainEngine, - const api::BitcoinLikeNetworkParameters ¶ms) const { + std::vector BitcoinLikeAddress::getVersionFromKeychainEngine(const std::string &keychainEngine, + const api::BitcoinLikeNetworkParameters ¶ms) { if (keychainEngine == api::KeychainEngines::BIP32_P2PKH) { return params.P2PKHVersion; @@ -75,6 +75,9 @@ namespace ledger { } else if (keychainEngine == api::KeychainEngines::BIP173_P2WSH) { auto bech32Params = Bech32Parameters::getBech32Params(params.Identifier); return bech32Params.P2WSHVersion; + } else if (keychainEngine == api::KeychainEngines::BIP350_P2TR) { + auto bech32Params = Bech32Parameters::getBech32Params(params.Identifier); + return bech32Params.P2TRVersion; } throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Invalid Keychain Engine: ", keychainEngine); }; @@ -88,22 +91,11 @@ namespace ledger { } std::string BitcoinLikeAddress::toBase58() { - auto config = std::make_shared(); - config->putString("networkIdentifier", _params.Identifier); - return Base58::encodeWithChecksum(vector::concat(getVersionFromKeychainEngine(_keychainEngine, _params), _hash160), config); - } - - std::string toBech32Helper(const std::string &keychainEngine, - const std::vector &hash160, - const api::BitcoinLikeNetworkParameters ¶ms) { - - auto bech32 = Bech32Factory::newBech32Instance(params.Identifier).getValue(); - auto witnessVersion = (keychainEngine == api::KeychainEngines::BIP173_P2WPKH) ? bech32->getBech32Params().P2WPKHVersion : bech32->getBech32Params().P2WSHVersion; - return bech32->encode(hash160, witnessVersion); + return toBase58Impl(); } std::string BitcoinLikeAddress::toBech32() { - return toBech32Helper(_keychainEngine, _hash160, _params); + return toBech32Impl(); } bool BitcoinLikeAddress::isP2SH() { @@ -122,21 +114,37 @@ namespace ledger { return _keychainEngine == api::KeychainEngines::BIP173_P2WPKH; } + bool BitcoinLikeAddress::isP2TR() { + return _keychainEngine == api::KeychainEngines::BIP350_P2TR; + } + std::experimental::optional BitcoinLikeAddress::getDerivationPath() { return _derivationPath.toOptional(); } - std::string BitcoinLikeAddress::toBase58() const { + std::string BitcoinLikeAddress::toBase58Impl() const { if (_keychainEngine != api::KeychainEngines::BIP32_P2PKH && _keychainEngine != api::KeychainEngines::BIP49_P2SH) { - throw Exception(api::ErrorCode::INVALID_BASE58_FORMAT, "Base58 format only available for api::KeychainEngines::BIP32_P2PKH and api::KeychainEngines::BIP49_P2SH"); + throw Exception(api::ErrorCode::INVALID_BASE58_FORMAT, + "Base58 format only available for api::KeychainEngines::BIP32_P2PKH and api::KeychainEngines::BIP49_P2SH"); } + auto config = std::make_shared(); config->putString("networkIdentifier", _params.Identifier); return Base58::encodeWithChecksum(vector::concat(getVersionFromKeychainEngine(_keychainEngine, _params), _hash160), config); } - std::string BitcoinLikeAddress::toBech32() const { - return toBech32Helper(_keychainEngine, _hash160, _params); + std::string BitcoinLikeAddress::toBech32Impl() const { + if (_keychainEngine != api::KeychainEngines::BIP173_P2WPKH + && _keychainEngine != api::KeychainEngines::BIP173_P2WSH + && _keychainEngine != api::KeychainEngines::BIP350_P2TR) { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, + "Bech32 format only available for api::KeychainEngines::BIP173_P2WPKH/P2WSH and api::KeychainEngines::BIP350_P2TR"); + } + + auto bech32 = Bech32Factory::newBech32Instance(_params.Identifier).getValue(); + std::vector witnessVersion + = BitcoinLikeAddress::getVersionFromKeychainEngine(_keychainEngine, _params); + return bech32->encode(_hash160, witnessVersion); } std::shared_ptr @@ -156,10 +164,16 @@ namespace ledger { } std::string BitcoinLikeAddress::getStringAddress() const { - if (_keychainEngine == api::KeychainEngines::BIP173_P2WPKH || _keychainEngine == api::KeychainEngines::BIP173_P2WSH) { - return toBech32(); + if (_keychainEngine == api::KeychainEngines::BIP173_P2WPKH + || _keychainEngine == api::KeychainEngines::BIP173_P2WSH + || _keychainEngine == api::KeychainEngines::BIP350_P2TR) { + return toBech32Impl(); } - return toBase58(); + return toBase58Impl(); + } + + const std::string& BitcoinLikeAddress::getKeychainEngine() const { + return _keychainEngine; } std::shared_ptr BitcoinLikeAddress::fromBase58(const std::string &address, @@ -191,10 +205,41 @@ namespace ledger { std::shared_ptr BitcoinLikeAddress::fromBech32(const std::string& address, const api::Currency& currency, const Option& derivationPath) { - auto& params = currency.bitcoinLikeNetworkParameters.value(); - auto bech32 = Bech32Factory::newBech32Instance(params.Identifier).getValue(); - auto decoded = bech32->decode(address); - auto keychainEngine = (decoded.second.size() == 32 || decoded.first == bech32->getBech32Params().P2WSHVersion) ? api::KeychainEngines::BIP173_P2WSH : api::KeychainEngines::BIP173_P2WPKH; + const auto& params = currency.bitcoinLikeNetworkParameters.value(); + const auto bech32 = Bech32Factory::newBech32Instance(params.Identifier).getValue(); + const auto decoded = bech32->decode(address); + std::string keychainEngine; + const auto bech32_params = bech32->getBech32Params(); + const size_t payload_sz = decoded.second.size(); + if (bech32_params.P2WSHVersion == bech32_params.P2WPKHVersion) { + // Bitcoin case (both are equal to zero) => detect keychain engine by length + if (decoded.first == bech32_params.P2WSHVersion) { + if (payload_sz == 32) { + keychainEngine = api::KeychainEngines::BIP173_P2WSH; + } else if (payload_sz == 20) { + keychainEngine = api::KeychainEngines::BIP173_P2WPKH; + } else { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Unexpected wintess program length"); + } + } else if (decoded.first == bech32_params.P2TRVersion && payload_sz == 32) { + keychainEngine = api::KeychainEngines::BIP350_P2TR; + } else { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Unknown form of Bech32 format (Bitcoin like)"); + } + } else { + // Bitcoin Cash case (P2WPKH is 0 and P2WSH is 8), length of hash can be different + // https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md + if (decoded.first == bech32_params.P2WSHVersion && (payload_sz == 32 || payload_sz == 20)) { + keychainEngine = api::KeychainEngines::BIP173_P2WSH; + } else if (decoded.first == bech32_params.P2WPKHVersion && payload_sz == 20) { + keychainEngine = api::KeychainEngines::BIP173_P2WPKH; + } else if (decoded.first == bech32_params.P2TRVersion && payload_sz == 32) { + keychainEngine = api::KeychainEngines::BIP350_P2TR; + } else { + throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Unknown form of Bech32 format (BCH)"); + } + } + return std::make_shared(currency, decoded.second, keychainEngine, @@ -210,7 +255,6 @@ namespace ledger { } else if (keychainEngine == api::KeychainEngines::BIP49_P2SH || keychainEngine == api::KeychainEngines::BIP173_P2WSH) { auto hash160 = fromPublicKeyToHash160(pubKey->derivePublicKey(derivationPath), pubKey->deriveHash160(derivationPath), currency, keychainEngine); return BitcoinLikeAddress(currency, hash160, keychainEngine).toString(); - } throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Invalid Keychain Engine: ", keychainEngine); } diff --git a/core/src/bitcoin/BitcoinLikeAddress.hpp b/core/src/bitcoin/BitcoinLikeAddress.hpp index 85cadd00f2..00b8aa2173 100644 --- a/core/src/bitcoin/BitcoinLikeAddress.hpp +++ b/core/src/bitcoin/BitcoinLikeAddress.hpp @@ -46,23 +46,22 @@ namespace ledger { const std::string &keychainEngine, const Option& derivationPath = Option()); virtual std::vector getVersion() override; - std::vector getVersionFromKeychainEngine(const std::string &keychainEngine, - const api::BitcoinLikeNetworkParameters ¶ms) const; virtual std::vector getHash160() override; virtual api::BitcoinLikeNetworkParameters getNetworkParameters() override; virtual std::string toBase58() override; virtual std::string toBech32() override; - std::string toBase58() const; - std::string toBech32() const; virtual bool isP2SH() override; virtual bool isP2PKH() override; virtual bool isP2WSH() override; virtual bool isP2WPKH() override; + virtual bool isP2TR() override; virtual optional getDerivationPath() override; std::string toString() override; std::string getStringAddress() const; + const std::string& getKeychainEngine() const; + static std::shared_ptr parse(const std::string& address, const api::Currency& currency, const Option& derivationPath = Option()); static std::shared_ptr fromBase58(const std::string& address, @@ -87,6 +86,11 @@ namespace ledger { const api::Currency ¤cy, const std::string &keychainEngine); + protected: + std::string toBase58Impl() const; + std::string toBech32Impl() const; + static std::vector getVersionFromKeychainEngine(const std::string &keychainEngine, + const api::BitcoinLikeNetworkParameters ¶ms); private: const std::vector _hash160; diff --git a/core/src/jni/jni/BitcoinLikeAddress.cpp b/core/src/jni/jni/BitcoinLikeAddress.cpp index f190e62b9f..8c103e5b5d 100644 --- a/core/src/jni/jni/BitcoinLikeAddress.cpp +++ b/core/src/jni/jni/BitcoinLikeAddress.cpp @@ -110,4 +110,14 @@ CJNIEXPORT jboolean JNICALL Java_co_ledger_core_BitcoinLikeAddress_00024CppProxy } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */) } +CJNIEXPORT jboolean JNICALL Java_co_ledger_core_BitcoinLikeAddress_00024CppProxy_native_1isP2TR(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef) +{ + try { + DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef); + const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::BitcoinLikeAddress>(nativeRef); + auto r = ref->isP2TR(); + return ::djinni::release(::djinni::Bool::fromCpp(jniEnv, r)); + } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */) +} + } // namespace djinni_generated diff --git a/core/src/wallet/bitcoin/keychains/BitcoinLikeKeychain.cpp b/core/src/wallet/bitcoin/keychains/BitcoinLikeKeychain.cpp index e9563e6d9c..99af5fd8a3 100644 --- a/core/src/wallet/bitcoin/keychains/BitcoinLikeKeychain.cpp +++ b/core/src/wallet/bitcoin/keychains/BitcoinLikeKeychain.cpp @@ -113,12 +113,14 @@ namespace ledger { bool BitcoinLikeKeychain::isSegwit(const std::string &keychainEngine) { return keychainEngine == api::KeychainEngines::BIP49_P2SH || keychainEngine == api::KeychainEngines::BIP173_P2WPKH || - keychainEngine == api::KeychainEngines::BIP173_P2WSH; + keychainEngine == api::KeychainEngines::BIP173_P2WSH || + keychainEngine == api::KeychainEngines::BIP350_P2TR; } bool BitcoinLikeKeychain::isNativeSegwit(const std::string &keychainEngine) { return keychainEngine == api::KeychainEngines::BIP173_P2WPKH || - keychainEngine == api::KeychainEngines::BIP173_P2WSH; + keychainEngine == api::KeychainEngines::BIP173_P2WSH || + keychainEngine == api::KeychainEngines::BIP350_P2TR; } } -} \ No newline at end of file +} diff --git a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp index 742830600a..db050e1ba8 100644 --- a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp +++ b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp @@ -94,6 +94,10 @@ namespace ledger { return state; } + std::string CommonBitcoinLikeKeychains::getKeychainEngine() const { + return _keychainEngine; + } + bool CommonBitcoinLikeKeychains::markPathAsUsed(const DerivationPath &p, bool needExtendKeychain) { KeychainPersistentState state = getState(); DerivationPath path(p); diff --git a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp index 81735fa703..5cd2626b92 100644 --- a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp +++ b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp @@ -95,6 +95,8 @@ namespace ledger { */ KeychainPersistentState getState() const; + std::string getKeychainEngine() const; + protected: std::shared_ptr _internalNodeXpub; std::shared_ptr _publicNodeXpub; diff --git a/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp b/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp index 9bb7622569..81d09aaa6d 100644 --- a/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp +++ b/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp @@ -184,6 +184,8 @@ namespace ledger { script << btccore::OP_HASH160 << a->getHash160() << btccore::OP_EQUAL; } else if (a->isP2WPKH() || a->isP2WSH()) { script << btccore::OP_0 << a->getHash160(); + } else if (a->isP2TR()) { + script << btccore::OP_1 << a->getHash160(); } else { throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Cannot create output script from {}.", address); } diff --git a/core/test/bitcoin/address_test.cpp b/core/test/bitcoin/address_test.cpp index 4cedac8bea..a03bdfeb73 100644 --- a/core/test/bitcoin/address_test.cpp +++ b/core/test/bitcoin/address_test.cpp @@ -63,15 +63,19 @@ TEST(Address, AddressFromBase58String) { auto x = currency.bitcoinLikeNetworkParameters.value(); for (auto& item : fixtures) { EXPECT_TRUE(Address::isValid(item[1], currency)); - auto address = Address::parse(item[1], currency)->asBitcoinLikeAddress(); - EXPECT_EQ(address->getHash160(), hex::toByteArray(item[0])); - EXPECT_EQ(address->getVersion(), hex::toByteArray(item[3])); + auto address_from_base58 = Address::parse(item[1], currency)->asBitcoinLikeAddress(); + auto address_from_bech32 = Address::parse(item[2], currency)->asBitcoinLikeAddress(); + EXPECT_EQ(address_from_base58->getHash160(), hex::toByteArray(item[0])); + EXPECT_EQ(address_from_base58->getVersion(), hex::toByteArray(item[3])); + EXPECT_EQ(address_from_bech32->getHash160(), hex::toByteArray(item[0])); + if (item[3] == "05") { - EXPECT_TRUE(address->isP2SH()); + EXPECT_TRUE(address_from_base58->isP2SH()); } else { - EXPECT_TRUE(address->isP2PKH()); + EXPECT_TRUE(address_from_base58->isP2PKH()); + EXPECT_EQ(address_from_bech32->getVersion(), hex::toByteArray(item[3])); } - EXPECT_EQ(address->toBech32(), item[2]); + EXPECT_TRUE(address_from_bech32->isP2WPKH()); } } @@ -98,11 +102,16 @@ TEST(Address, XpubFromBase58StringToBech32) { config->putString(api::Configuration::KEYCHAIN_ENGINE, api::KeychainEngines::BIP173_P2WPKH); auto xpub = ledger::core::BitcoinLikeExtendedPublicKey::fromBase58(currency, xpubStr, optional("49'/145'/0'"), config); EXPECT_EQ(xpub->toBase58(), xpubStr); - EXPECT_EQ(xpub->derive("0/0")->toBase58(), base58Address); EXPECT_EQ(xpub->derive("0/0")->toBech32(), bech32Address); + config->putString(api::Configuration::KEYCHAIN_ENGINE, api::KeychainEngines::BIP32_P2PKH); + xpub = ledger::core::BitcoinLikeExtendedPublicKey::fromBase58(currency, xpubStr, optional("49'/145'/0'"), config); + EXPECT_EQ(xpub->toBase58(), xpubStr); + EXPECT_EQ(xpub->derive("0/0")->toBase58(), base58Address); + auto addr = ledger::core::BitcoinLikeAddress::fromBech32(bech32Address, currency); - EXPECT_EQ(addr->toBase58(), base58Address); + auto addr_p2pkh = ledger::core::BitcoinLikeAddress(currency, addr->getHash160(), api::KeychainEngines::BIP32_P2PKH); + EXPECT_EQ(addr_p2pkh.toBase58(), base58Address); } // Test writting based on NanoS of QA @@ -148,15 +157,20 @@ TEST(Address, XpubFromBase58StringToBech32LTC) { TEST(Address, FromBech32Address) { //https://github.com/bitcoincashjs/cashaddrjs/blob/master/test/cashaddr.js std::vector> tests = { - {"tb1qunawpra24prfc46klknlhl0ydy32feajmwpg84", currencies::BITCOIN_TESTNET},//BTC P2WPKH - {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", currencies::BITCOIN},//BTC P2WSH - {"bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", currencies::BITCOIN_CASH},//BCH P2WPKH - {"bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq", currencies::BITCOIN_CASH},//BCH P2WSH - {"dgb1qgdg3hdysnpmaxpdpqqzhey2f5888av488hq0z6", currencies::DIGIBYTE},//DGB P2WPKH - {"ltc1q7qnj9xm8wp8ucmg64lk0h03as8k6ql6rk4wvsd", currencies::LITECOIN}//LTC P2WPKH + {"tb1qunawpra24prfc46klknlhl0ydy32feajmwpg84", currencies::BITCOIN_TESTNET}, // BTC P2WPKH + {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", currencies::BITCOIN}, // BTC P2WSH + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", currencies::BITCOIN}, // BTC P2WPKH + {"tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", currencies::BITCOIN_TESTNET}, // BTC P2WPKH + {"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", currencies::BITCOIN_TESTNET}, // BTC P2WSH + {"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", currencies::BITCOIN}, // BTC P2TR (BIP350) + {"bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", currencies::BITCOIN_CASH}, // BCH P2WPKH + {"bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq", currencies::BITCOIN_CASH}, // BCH P2WSH + {"dgb1qgdg3hdysnpmaxpdpqqzhey2f5888av488hq0z6", currencies::DIGIBYTE}, // DGB P2WPKH + {"ltc1q7qnj9xm8wp8ucmg64lk0h03as8k6ql6rk4wvsd", currencies::LITECOIN} // LTC P2WPKH }; for (auto &test : tests) { + std::cerr << test.first << std::endl; auto address = ledger::core::BitcoinLikeAddress::fromBech32(test.first, test.second); EXPECT_EQ(address->toBech32(), test.first); } -} \ No newline at end of file +} diff --git a/core/test/integration/keychains/keychain_test_helper.h b/core/test/integration/keychains/keychain_test_helper.h index 0751c7284e..0af48b0bda 100644 --- a/core/test/integration/keychains/keychain_test_helper.h +++ b/core/test/integration/keychains/keychain_test_helper.h @@ -128,8 +128,8 @@ class KeychainFixture : public BaseFixture { void testKeychain(const KeychainTestData &data, std::shared_ptr backend, std::function f) { auto configuration = std::make_shared(); - dispatcher->getMainExecutionContext()->execute(ledger::core::make_runnable([=]() { - Keychain keychain( + { + Keychain temp_keychain( configuration, data.currency, 0, @@ -139,10 +139,21 @@ class KeychainFixture : public BaseFixture { configuration), std::make_shared(*backend, randomKeychainName()) ); - f(keychain); - dispatcher->stop(); - })); - dispatcher->waitUntilStopped(); + configuration->putString(api::Configuration::KEYCHAIN_ENGINE, temp_keychain.getKeychainEngine()); + } + + Keychain keychain( + configuration, + data.currency, + 0, + ledger::core::BitcoinLikeExtendedPublicKey::fromBase58(data.currency, + data.xpub, + optional(data.derivationPath), + configuration), + std::make_shared(*backend, randomKeychainName()) + ); + + f(keychain); }; }; From 540f1efe1d254fedaed0dfbcfee449375ac2e084 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Fri, 6 May 2022 12:55:40 +0200 Subject: [PATCH 04/24] string -> const string& , protected -> private --- core/src/bitcoin/BitcoinLikeAddress.hpp | 3 +-- .../wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp | 2 +- .../wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/bitcoin/BitcoinLikeAddress.hpp b/core/src/bitcoin/BitcoinLikeAddress.hpp index 00b8aa2173..6a6ac238e6 100644 --- a/core/src/bitcoin/BitcoinLikeAddress.hpp +++ b/core/src/bitcoin/BitcoinLikeAddress.hpp @@ -86,13 +86,12 @@ namespace ledger { const api::Currency ¤cy, const std::string &keychainEngine); - protected: + private: std::string toBase58Impl() const; std::string toBech32Impl() const; static std::vector getVersionFromKeychainEngine(const std::string &keychainEngine, const api::BitcoinLikeNetworkParameters ¶ms); - private: const std::vector _hash160; const api::BitcoinLikeNetworkParameters _params; const Option _derivationPath; diff --git a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp index db050e1ba8..42c881b674 100644 --- a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp +++ b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.cpp @@ -94,7 +94,7 @@ namespace ledger { return state; } - std::string CommonBitcoinLikeKeychains::getKeychainEngine() const { + const std::string& CommonBitcoinLikeKeychains::getKeychainEngine() const { return _keychainEngine; } diff --git a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp index 5cd2626b92..8131209f38 100644 --- a/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp +++ b/core/src/wallet/bitcoin/keychains/CommonBitcoinLikeKeychains.hpp @@ -95,7 +95,7 @@ namespace ledger { */ KeychainPersistentState getState() const; - std::string getKeychainEngine() const; + const std::string& getKeychainEngine() const; protected: std::shared_ptr _internalNodeXpub; From c89ded7b5d6dd64fd68b2d18a46ddd6823fb56e6 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Fri, 6 May 2022 16:24:58 +0200 Subject: [PATCH 05/24] Refactoring: add BitcoinLikeAddress::isKeychainEngineBech32Compatible --- core/src/bitcoin/BitcoinLikeAddress.cpp | 14 ++++++++------ core/src/bitcoin/BitcoinLikeAddress.hpp | 3 +++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/bitcoin/BitcoinLikeAddress.cpp b/core/src/bitcoin/BitcoinLikeAddress.cpp index c99534b96c..e168c95137 100644 --- a/core/src/bitcoin/BitcoinLikeAddress.cpp +++ b/core/src/bitcoin/BitcoinLikeAddress.cpp @@ -122,6 +122,12 @@ namespace ledger { return _derivationPath.toOptional(); } + bool BitcoinLikeAddress::isKeychainEngineBech32Compatible() const { + return _keychainEngine == api::KeychainEngines::BIP173_P2WPKH + || _keychainEngine == api::KeychainEngines::BIP173_P2WSH + || _keychainEngine == api::KeychainEngines::BIP350_P2TR; + } + std::string BitcoinLikeAddress::toBase58Impl() const { if (_keychainEngine != api::KeychainEngines::BIP32_P2PKH && _keychainEngine != api::KeychainEngines::BIP49_P2SH) { throw Exception(api::ErrorCode::INVALID_BASE58_FORMAT, @@ -134,9 +140,7 @@ namespace ledger { } std::string BitcoinLikeAddress::toBech32Impl() const { - if (_keychainEngine != api::KeychainEngines::BIP173_P2WPKH - && _keychainEngine != api::KeychainEngines::BIP173_P2WSH - && _keychainEngine != api::KeychainEngines::BIP350_P2TR) { + if (!isKeychainEngineBech32Compatible()) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Bech32 format only available for api::KeychainEngines::BIP173_P2WPKH/P2WSH and api::KeychainEngines::BIP350_P2TR"); } @@ -164,9 +168,7 @@ namespace ledger { } std::string BitcoinLikeAddress::getStringAddress() const { - if (_keychainEngine == api::KeychainEngines::BIP173_P2WPKH - || _keychainEngine == api::KeychainEngines::BIP173_P2WSH - || _keychainEngine == api::KeychainEngines::BIP350_P2TR) { + if (isKeychainEngineBech32Compatible()) { return toBech32Impl(); } return toBase58Impl(); diff --git a/core/src/bitcoin/BitcoinLikeAddress.hpp b/core/src/bitcoin/BitcoinLikeAddress.hpp index 6a6ac238e6..491b11afdd 100644 --- a/core/src/bitcoin/BitcoinLikeAddress.hpp +++ b/core/src/bitcoin/BitcoinLikeAddress.hpp @@ -89,6 +89,9 @@ namespace ledger { private: std::string toBase58Impl() const; std::string toBech32Impl() const; + + bool isKeychainEngineBech32Compatible() const; + static std::vector getVersionFromKeychainEngine(const std::string &keychainEngine, const api::BitcoinLikeNetworkParameters ¶ms); From 18c393d0c99cf6cefb5a28fda68c750c80b23a1b Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Mon, 9 May 2022 08:42:38 +0200 Subject: [PATCH 06/24] Add BitcoinLikeScript::isP2TR --- core/src/bitcoin/BitcoinLikeAddress.cpp | 2 ++ core/src/wallet/bitcoin/BitcoinLikeWallet.cpp | 3 ++- .../src/wallet/bitcoin/scripts/BitcoinLikeScript.cpp | 12 +++++++++++- core/src/wallet/bitcoin/scripts/BitcoinLikeScript.h | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/src/bitcoin/BitcoinLikeAddress.cpp b/core/src/bitcoin/BitcoinLikeAddress.cpp index e168c95137..d9eb47b7cf 100644 --- a/core/src/bitcoin/BitcoinLikeAddress.cpp +++ b/core/src/bitcoin/BitcoinLikeAddress.cpp @@ -258,6 +258,7 @@ namespace ledger { auto hash160 = fromPublicKeyToHash160(pubKey->derivePublicKey(derivationPath), pubKey->deriveHash160(derivationPath), currency, keychainEngine); return BitcoinLikeAddress(currency, hash160, keychainEngine).toString(); } + // keychainEngine == BIP350_P2TR: we don't create Taproot addresses from extended pubkey. throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Invalid Keychain Engine: ", keychainEngine); } @@ -283,6 +284,7 @@ namespace ledger { witnessScript.push_back(btccore::OP_CHECKSIG); return SHA256::bytesToBytesHash(witnessScript); } + // keychainEngine == BIP350_P2TR: we don't create a hash160 from the Taproot public key throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Invalid Keychain Engine: ", keychainEngine); } diff --git a/core/src/wallet/bitcoin/BitcoinLikeWallet.cpp b/core/src/wallet/bitcoin/BitcoinLikeWallet.cpp index f7f246d54f..ea3465d2b0 100644 --- a/core/src/wallet/bitcoin/BitcoinLikeWallet.cpp +++ b/core/src/wallet/bitcoin/BitcoinLikeWallet.cpp @@ -181,7 +181,8 @@ namespace ledger { info.derivations.push_back(xpubPath.toString()); info.owners.push_back(std::string("main")); } else { - throw make_exception(api::ErrorCode::IMPLEMENTATION_IS_MISSING, "No implementation found found for keychain {}", keychainEngine); + // keychainEngine == BIP350_P2TR: no reason to implement this operation for Taproot + throw make_exception(api::ErrorCode::IMPLEMENTATION_IS_MISSING, "No implementation found for keychain {}", keychainEngine); } return info; diff --git a/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.cpp b/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.cpp index 08bbe734f2..103c7958d0 100644 --- a/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.cpp +++ b/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.cpp @@ -177,6 +177,13 @@ namespace ledger { return (size() == 2 && (*this)[0].isEqualTo(btccore::OP_0) && (*this)[1].sizeEqualsTo(32)); } + bool BitcoinLikeScript::isP2TR() const { + if (_configuration.isSigned) { + return _configuration.keychainEngine == api::KeychainEngines::BIP350_P2TR; + } + return (size() == 2 && (*this)[0].isEqualTo(btccore::OP_1) && (*this)[1].sizeEqualsTo(32)); + } + std::size_t BitcoinLikeScript::size() const { return _chunks.size(); } @@ -222,11 +229,14 @@ namespace ledger { // return Option( BitcoinLikeAddress(currency, (*this)[1].getBytes(), api::KeychainEngines::BIP173_P2WSH)); + } else if (isP2TR()) { + // + return Option( + BitcoinLikeAddress(currency, (*this)[1].getBytes(), api::KeychainEngines::BIP350_P2TR)); } return Option(); } - BitcoinLikeScriptChunk::BitcoinLikeScriptChunk(BitcoinLikeScriptOpCode op) : _value(op) { } diff --git a/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.h b/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.h index c640ea7352..e459c83931 100644 --- a/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.h +++ b/core/src/wallet/bitcoin/scripts/BitcoinLikeScript.h @@ -119,6 +119,8 @@ namespace ledger { bool isP2WSH() const; + bool isP2TR() const; + Option parseAddress(const api::Currency ¤cy) const; static Try parse(const std::vector &script, From 2bb7a047f39937ca56c8000c091682623a8ec170 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Tue, 10 May 2022 09:30:35 +0200 Subject: [PATCH 07/24] Naming magic values --- core/src/bitcoin/bech32/BTCBech32.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/bitcoin/bech32/BTCBech32.cpp b/core/src/bitcoin/bech32/BTCBech32.cpp index 46d73252e7..6fa935faf0 100644 --- a/core/src/bitcoin/bech32/BTCBech32.cpp +++ b/core/src/bitcoin/bech32/BTCBech32.cpp @@ -34,6 +34,10 @@ namespace ledger { namespace core { + namespace { + const uint32_t bech32mParam = 0x2bc830a3; + } + uint64_t BTCBech32::polymod(const std::vector& values) const { uint32_t chk = 1; for (size_t i = 0; i < values.size(); ++i) { @@ -72,7 +76,7 @@ namespace ledger { if (version == _bech32Params.P2WPKHVersion || version == _bech32Params.P2WSHVersion) { return encodeBech32(converted, 1); } else if (version[0] <= 16) { - return encodeBech32(converted, 0x2bc830a3); + return encodeBech32(converted, bech32mParam); } throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid Bech32 version value : must be in the range 0..16 inclusive"); } @@ -98,7 +102,7 @@ namespace ledger { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Bech32 checksum verification failed"); } } else if (version[0] <= 16) { - if (!verifyChecksum(decoded.second, 0x2bc830a3)) { + if (!verifyChecksum(decoded.second, bech32mParam)) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Bech32M checksum verification failed"); } } else { From e48f1490957ec86ded148c641c3f858b14ff87ef Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Tue, 10 May 2022 10:51:08 +0200 Subject: [PATCH 08/24] Add const --- core/src/bitcoin/bech32/BTCBech32.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/bitcoin/bech32/BTCBech32.cpp b/core/src/bitcoin/bech32/BTCBech32.cpp index 6fa935faf0..a24d4340d4 100644 --- a/core/src/bitcoin/bech32/BTCBech32.cpp +++ b/core/src/bitcoin/bech32/BTCBech32.cpp @@ -85,24 +85,24 @@ namespace ledger { std::pair, std::vector> BTCBech32::decode(const std::string& str) const { - auto decoded = decodeBech32Raw(str); - - if (decoded.second.size() < 1) { + const auto decoded = decodeBech32Raw(str); + const auto& decoded_payload = decoded.second; + if (decoded_payload.size() < 1) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : invalid Bech32 format"); } - std::vector version{decoded.second[0]}; + const std::vector version{decoded_payload[0]}; - if (decoded.second.size() < _bech32Params.checksumSize) { + if (decoded_payload.size() < _bech32Params.checksumSize) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : the address is less than checksum size"); } if (version[0] == 0) { - if (!verifyChecksum(decoded.second, 1)) { + if (!verifyChecksum(decoded_payload, 1)) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Bech32 checksum verification failed"); } } else if (version[0] <= 16) { - if (!verifyChecksum(decoded.second, bech32mParam)) { + if (!verifyChecksum(decoded_payload, bech32mParam)) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Bech32M checksum verification failed"); } } else { @@ -110,7 +110,7 @@ namespace ledger { } // strip the checksum - std::vector decoded_address(decoded.second.begin(), decoded.second.end() - _bech32Params.checksumSize); + const std::vector decoded_address(decoded_payload.begin(), decoded_payload.end() - _bech32Params.checksumSize); if (decoded.first != _bech32Params.hrp) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : Invalid Bech32 hrp"); @@ -118,11 +118,11 @@ namespace ledger { std::vector converted; int fromBits = 5, toBits = 8; bool pad = false; - auto result = Bech32::convertBits(std::vector(decoded_address.begin() + 1, decoded_address.end()), - fromBits, - toBits, - pad, - converted); + const auto result = Bech32::convertBits(std::vector(decoded_address.begin() + 1, decoded_address.end()), + fromBits, + toBits, + pad, + converted); if (!result || converted.size() < 2 || converted.size() > 40 || version.size() != 1) { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Invalid address : Invalid Bech32 format"); From 5a64dd3472d9d42fc3f3ca460606cf20e32606f3 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Tue, 10 May 2022 17:17:36 +0200 Subject: [PATCH 09/24] Database migration procedures --- core/src/database/DatabaseSessionPool.hpp | 2 +- core/src/database/migrations.cpp | 31 ++++++++++++++++++----- core/src/database/migrations.hpp | 3 +++ core/src/math/bech32/Bech32Parameters.cpp | 6 +++-- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/core/src/database/DatabaseSessionPool.hpp b/core/src/database/DatabaseSessionPool.hpp index 208dd21a80..15322e6ce5 100644 --- a/core/src/database/DatabaseSessionPool.hpp +++ b/core/src/database/DatabaseSessionPool.hpp @@ -61,7 +61,7 @@ namespace ledger { const std::string &password = "" ); - static const int CURRENT_DATABASE_SCHEME_VERSION = 28; + static const int CURRENT_DATABASE_SCHEME_VERSION = 29; void performDatabaseMigration(); void performDatabaseRollback(); diff --git a/core/src/database/migrations.cpp b/core/src/database/migrations.cpp index dd9d72ab39..5f9eb0163d 100644 --- a/core/src/database/migrations.cpp +++ b/core/src/database/migrations.cpp @@ -358,7 +358,6 @@ namespace ledger { "symbol VARCHAR(255) NOT NULL," "number_of_decimal INTEGER NOT NULL" ")"; - } template <> void rollback<5>(soci::session& sql, api::DatabaseBackendType type) { @@ -734,7 +733,6 @@ namespace ledger { } template <> void rollback<18>(soci::session& sql, api::DatabaseBackendType type) { - } template <> void migrate<19>(soci::session& sql, api::DatabaseBackendType type) { @@ -813,7 +811,6 @@ namespace ledger { "type INTEGER NOT NULL" ")"; - // Stellar account operations sql << "CREATE TABLE stellar_account_operations(" "uid VARCHAR(255) PRIMARY KEY NOT NULL REFERENCES operations(uid) ON DELETE CASCADE," @@ -825,7 +822,6 @@ namespace ledger { "base_fee VARCHAR(255) NOT NULL," "base_reserve VARCHAR(255) NOT NULL" ")"; - } template <> void rollback<19>(soci::session& sql, api::DatabaseBackendType type) { @@ -955,10 +951,8 @@ namespace ledger { sql << "DROP TABLE cosmos_transactions"; sql << "DROP TABLE cosmos_accounts"; sql << "DROP TABLE cosmos_currencies"; - } - template <> void migrate<21>(soci::session& sql, api::DatabaseBackendType type) { sql << "ALTER TABLE bitcoin_outputs ADD replaceable INTEGER DEFAULT 0"; } @@ -1143,7 +1137,6 @@ namespace ledger { } else { sql << "UPDATE bitcoin_currencies SET dust_amount = 546 WHERE identifier = 'btc'"; sql << "UPDATE bitcoin_currencies SET dust_amount = 546 WHERE identifier = 'btc_testnet'"; - } } @@ -1205,5 +1198,29 @@ namespace ledger { sql << "DROP INDEX bitcoin_operations_transaction_uid_index ;"; } + template <> void migrate<29>(soci::session& sql, api::DatabaseBackendType type) { + sql << "ALTER TABLE bech32_parameters ADD p2trversion VARCHAR(255) DEFAULT 0x01;"; + } + + template <> void rollback<29>(soci::session& sql, api::DatabaseBackendType type) { + // SQLite doesn't handle ALTER TABLE DROP + if (type != api::DatabaseBackendType::SQLITE3) { + sql << "ALTER TABLE bech32_parameters DROP p2trversion"; + } else { + sql << "CREATE TABLE bech32_parameters_swap(" + "name VARCHAR(255) PRIMARY KEY NOT NULL REFERENCES bitcoin_currencies(name) ON DELETE CASCADE ON UPDATE CASCADE," + "hrp VARCHAR(255) NOT NULL," + "separator VARCHAR(255) NOT NULL," + "generator VARCHAR(255) NOT NULL," + "p2wpkh_version VARCHAR(255) NOT NULL," + "p2wsh_version VARCHAR(255) NOT NULL" + ")"; + sql << "INSERT INTO bech32_parameters_swap " + "SELECT name, hrp, separator, generator, p2wpkh_version, p2wsh_version " + "FROM bech32_parameters"; + sql << "DROP TABLE bech32_parameters"; + sql << "ALTER TABLE bech32_parameters_swap RENAME TO bech32_parameters"; + } + } } } diff --git a/core/src/database/migrations.hpp b/core/src/database/migrations.hpp index 4a331a774a..1f085d42c4 100644 --- a/core/src/database/migrations.hpp +++ b/core/src/database/migrations.hpp @@ -215,6 +215,9 @@ namespace ledger { template <> void migrate<28>(soci::session& sql, api::DatabaseBackendType type); template <> void rollback<28>(soci::session& sql, api::DatabaseBackendType type); + // add P2TRVersion field + template <> void migrate<29>(soci::session& sql, api::DatabaseBackendType type); + template <> void rollback<29>(soci::session& sql, api::DatabaseBackendType type); } } diff --git a/core/src/math/bech32/Bech32Parameters.cpp b/core/src/math/bech32/Bech32Parameters.cpp index ed6c74a10d..8d620d549c 100644 --- a/core/src/math/bech32/Bech32Parameters.cpp +++ b/core/src/math/bech32/Bech32Parameters.cpp @@ -146,14 +146,16 @@ namespace ledger { strings::join(strGenerator, generator, separator); auto P2WPKHVersion = hex::toString(params.P2WPKHVersion); auto P2WSHVersion = hex::toString(params.P2WSHVersion); + auto P2TRVersion = hex::toString(params.P2TRVersion); auto generatorStr = generator.str(); - sql << "INSERT INTO bech32_parameters VALUES(:name, :hrp, :separator, :generator, :p2wpkh_version, :p2wsh_version)", + sql << "INSERT INTO bech32_parameters VALUES(:name, :hrp, :separator, :generator, :p2wpkh_version, :p2wsh_version, :p2tr_version)", use(params.name), use(params.hrp), use(params.separator), use(generatorStr), use(P2WPKHVersion), - use(P2WSHVersion); + use(P2WSHVersion), + use(P2TRVersion); return true; } return false; From def6bba8f8b65d2580e52880cc0c8b0d84871ccd Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Wed, 11 May 2022 18:19:18 +0200 Subject: [PATCH 10/24] P2TR test --- ...akeP2TRTransaction.CreateP2TRWithOneOutput | 10 ++ ...akeP2TRTransaction.CreateP2TRWithOneOutput | 10 ++ .../bitcoin_P2TR_transaction_test.cpp | 129 ++++++++++++++++++ .../bitcoin_P2WPKH_transaction_tests.cpp | 38 +----- .../transactions/transaction_test_helper.cpp | 22 +++ .../transactions/transaction_test_helper.h | 3 + 6 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 core/test/integration/http_cache/BitcoinMainNetMakeP2TRTransaction.CreateP2TRWithOneOutput create mode 100644 core/test/integration/http_cache/BitcoinTestNetMakeP2TRTransaction.CreateP2TRWithOneOutput create mode 100644 core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp diff --git a/core/test/integration/http_cache/BitcoinMainNetMakeP2TRTransaction.CreateP2TRWithOneOutput b/core/test/integration/http_cache/BitcoinMainNetMakeP2TRTransaction.CreateP2TRWithOneOutput new file mode 100644 index 0000000000..d324c930a6 --- /dev/null +++ b/core/test/integration/http_cache/BitcoinMainNetMakeP2TRTransaction.CreateP2TRWithOneOutput @@ -0,0 +1,10 @@ +GET https://explorers.api.live.ledger.com/blockchain/v2/btc/blocks/current +0 +1 +{"hash":"00000000000000000008db19835048ecdd79d0974f4bd1a1d5492a5502f1d61c","height":722923,"time":"2022-02-12T11:49:27Z","txs":[]} + +GET https://explorers.api.live.ledger.com/timestamp +0 +1 +{"timestamp":1644666863} + diff --git a/core/test/integration/http_cache/BitcoinTestNetMakeP2TRTransaction.CreateP2TRWithOneOutput b/core/test/integration/http_cache/BitcoinTestNetMakeP2TRTransaction.CreateP2TRWithOneOutput new file mode 100644 index 0000000000..1c2b8854d5 --- /dev/null +++ b/core/test/integration/http_cache/BitcoinTestNetMakeP2TRTransaction.CreateP2TRWithOneOutput @@ -0,0 +1,10 @@ +GET https://explorers.api.live.ledger.com/blockchain/v2/btc_testnet/blocks/current +0 +1 +{"hash":"0000000000000062820b8f1f9932bc800017db055725b850b96270c75c3dd3e7","height":2223945,"time":"2022-05-11T15:40:14Z","txs":[]} + +GET https://explorers.api.live.ledger.com/timestamp +0 +1 +{"timestamp":1652283892} + diff --git a/core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp b/core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp new file mode 100644 index 0000000000..c03457b139 --- /dev/null +++ b/core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp @@ -0,0 +1,129 @@ +/* + * + * bitcoin_P2TR_transaction_tests + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Ledger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "../BaseFixture.h" +#include "../../fixtures/medium_xpub_fixtures.h" +#include +#include +#include "transaction_test_helper.h" +#include +#include +#include "../../fixtures/txes_to_wpkh_fixtures.h" + +using namespace std; + +struct BitcoinTestNetMakeP2TRTransaction : public BitcoinMakeBaseTransaction { + void SetUpConfig() override { + testData.configuration = DynamicObject::newInstance(); + testData.configuration->putString(api::Configuration::KEYCHAIN_ENGINE,api::KeychainEngines::BIP173_P2WPKH); + //https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki + testData.configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,"84'/'/'//
"); + testData.walletName = randomWalletName(); + testData.currencyName = "bitcoin_testnet"; + ledger::core::api::ExtendedKeyAccountCreationInfo TPUB_INFO( + 0, {"main"}, + {"84'/1'/0'"}, + {"tpubDDbUemsTt1GGoTRzXDa2wi3zdprJnynWUzidihCiw4r3wgumbpJR5Xzy892wtqgdkeHbZLh5EnJEDDdKBfE9CmQrwRu8guwrhBMHe53w1LF"} + ); + testData.inflate_btc = [TPUB_INFO](const std::shared_ptr& pool, + const std::shared_ptr& wallet) { + auto account + = std::dynamic_pointer_cast( + uv::wait(wallet->newAccountWithExtendedKeyInfo(TPUB_INFO))); + const std::string TX_1 + = "{\"hash\": \"417ff5706cf061d269f76635c7b30764d273bb1bff8daa3c7ff644c86c6ad56b\", \"received_at\": \"2022-01-26T15:34:10Z\", \"lock_time\": 2137675, \"fees\": 141, \"inputs\": [{\"output_hash\": \"2227ab854df67321b2df60a1af7359463398617d207c53ed2e42f9fe67c7177d\", \"output_index\": 0, \"input_index\": 0, \"value\": 2269417122, \"address\": \"tb1q438hxz0qvp5xwh9zux6fm5yuel9p7ckcxcm5mz\", \"script_signature\": \"\", \"txinwitness\":[\"304402200402edd3b96ef45c33b74f03b4663a15510887db359df618c239379aedfff9aa02205081b34c72c94fbf7c8b4b95d76c34d48605635ea938b557ce5f403cadabacb701\", \"02b50aeecd3f0beb47509ef1e51cc111970212979f63a1c8e7949202af128ada36\"], \"sequence\": 4294967294}], \"outputs\": [{\"output_index\": 0, \"value\": 10000, \"address\": \"tb1qtxs4uqwtgaxshgcaajpz3kl0lcxvph0rh8ymz6\", \"script_hex\": \"001459a15e01cb474d0ba31dec8228dbeffe0cc0dde3\"}, {\"output_index\": 1, \"value\": 2269406981, \"address\": \"tb1qa2cqctwg3efsa40hnrxz6ng95k929juhx60sl8\", \"script_hex\": \"0014eab00c2dc88e530ed5f798cc2d4d05a58aa2cb97\"}], \"block\": {\"hash\": \"0000000000000050e5ceaa95dbd83b74681c4c8faba72b8ec0efb6cbc17a563a\", \"height\": 2137676, \"time\": \"2022-01-26T15:34:10Z\"}, \"confirmations\": 87}"; + std::vector operations; + const auto parsedTx = ledger::core::JSONUtils::parse(TX_1); + account->interpretTransaction(*parsedTx, operations, true); + account->bulkInsert(operations); + return account; + }; + } +}; + +TEST_F(BitcoinTestNetMakeP2TRTransaction, CreateP2TRWithOneOutput) { + + std::vector input_descrs = { + { + "417ff5706cf061d269f76635c7b30764d273bb1bff8daa3c7ff644c86c6ad56b", + 0, + std::make_shared(BigInt(10000)) + } + }; + std::vector output_descrs = { + { + "tb1p2ruhtexffskh4r4kwdrza6xvpaga0aq9jjlt2gflkn4geymj697qlkqyqu", + hex::toByteArray("512050f975e4c94c2d7a8eb673462ee8cc0f51d7f40594beb5213fb4ea8c9372d17c"), + std::make_shared(BigInt(100)) + }, + { + "", // This is a change output. It isn't used for tx building. + hex::toByteArray("00147ab496acdfcc422f9486ccc7a66ddc4df2049811"), + std::make_shared(BigInt(994)) + } + }; + + createAndVerifyTransaction(input_descrs, output_descrs); +} + +struct BitcoinMainNetMakeP2TRTransaction : public BitcoinMakeBaseTransaction { + void SetUpConfig() override { + testData.configuration = DynamicObject::newInstance(); + testData.configuration->putString(api::Configuration::KEYCHAIN_ENGINE,api::KeychainEngines::BIP173_P2WPKH); + //https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki + testData.configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,"84'/'/'//
"); + testData.walletName = randomWalletName(); + testData.currencyName = "bitcoin"; + testData.inflate_btc = ledger::testing::txes_to_wpkh::inflate; + } +}; + +TEST_F(BitcoinMainNetMakeP2TRTransaction, CreateP2TRWithOneOutput) { + + std::vector input_descrs = { + { + "673f7e1155dd2cf61c961cedd24608274c0f20cfaeaa1154c2b5ef94ec7b81d1", + 1, + std::make_shared(BigInt(25402)) + } + }; + std::vector output_descrs = { + { + "bc1psf4kpzmrk9aqszdwfmtpqhmvhuhq69frx5efq64yqudq08p3u5vsq8wc5y", + hex::toByteArray("5120826b608b63b17a0809ae4ed6105f6cbf2e0d15233532906aa4071a079c31e519"), + std::make_shared(BigInt(100)) + }, + { + "", // This is a change output. It isn't used for tx building. + hex::toByteArray("00141017b1e1ca8632828f22a4d6c5260f3492b1dd08"), + std::make_shared(BigInt(16396)) + } + }; + + createAndVerifyTransaction(input_descrs, output_descrs); +} + diff --git a/core/test/integration/transactions/bitcoin_P2WPKH_transaction_tests.cpp b/core/test/integration/transactions/bitcoin_P2WPKH_transaction_tests.cpp index de1e0a36f2..0c6180c885 100644 --- a/core/test/integration/transactions/bitcoin_P2WPKH_transaction_tests.cpp +++ b/core/test/integration/transactions/bitcoin_P2WPKH_transaction_tests.cpp @@ -90,24 +90,7 @@ TEST_F(BitcoinMakeTransactionFromNativeSegwitToNativeSegwit, CreateStandardP2WPK } }; - std::shared_ptr generatedTx - = createTransaction(output_descrs); - - std::cerr << hex::toString(generatedTx->serialize()) << std::endl; - - EXPECT_TRUE(verifyTransaction(generatedTx, input_descrs, output_descrs)); - - std::vector tx_bin = generatedTx->serialize(); - std::cerr << hex::toString(generatedTx->serialize()) << std::endl; - - auto parsedTx - = BitcoinLikeTransactionBuilder::parseRawUnsignedTransaction(wallet->getCurrency(), - tx_bin, 0); - - EXPECT_TRUE(verifyTransactionOutputs(parsedTx, output_descrs)); - // Values in inputs are missing after parsing. Here we can test only outputs. - - EXPECT_EQ(tx_bin, parsedTx->serialize()); + createAndVerifyTransaction(input_descrs, output_descrs); } struct BitcoinMakeTransactionFromLegacyToNativeSegwit : public BitcoinMakeBaseTransaction { @@ -146,24 +129,7 @@ TEST_F(BitcoinMakeTransactionFromLegacyToNativeSegwit, CreateStandardP2WPKHWithO } }; - std::shared_ptr generatedTx - = createTransaction(output_descrs); - - std::cerr << "generated tx: " << std::endl - << hex::toString(generatedTx->serialize()) << std::endl; - - EXPECT_TRUE(verifyTransaction(generatedTx, input_descrs, output_descrs)); - - std::vector tx_bin = generatedTx->serialize(); - - auto parsedTx - = BitcoinLikeTransactionBuilder::parseRawUnsignedTransaction(wallet->getCurrency(), - tx_bin, 0); - - EXPECT_TRUE(verifyTransactionOutputs(parsedTx, output_descrs)); - // Values in inputs are missing after parsing. Here we can test only outputs. - - EXPECT_EQ(tx_bin, parsedTx->serialize()); + createAndVerifyTransaction(input_descrs, output_descrs); } TEST_F(BitcoinMakeTransactionFromLegacyToNativeSegwit, ParseSignedTx) { diff --git a/core/test/integration/transactions/transaction_test_helper.cpp b/core/test/integration/transactions/transaction_test_helper.cpp index 8ecd41622b..ad9f45dce4 100644 --- a/core/test/integration/transactions/transaction_test_helper.cpp +++ b/core/test/integration/transactions/transaction_test_helper.cpp @@ -124,3 +124,25 @@ bool BitcoinMakeBaseTransaction::verifyTransaction(std::shared_ptr& inputs, + const std::vector& outputs) { + std::shared_ptr generatedTx + = createTransaction(outputs); + + std::cerr << "generated tx: " << std::endl + << hex::toString(generatedTx->serialize()) << std::endl; + + EXPECT_TRUE(verifyTransaction(generatedTx, inputs, outputs)); + + std::vector tx_bin = generatedTx->serialize(); + + auto parsedTx + = BitcoinLikeTransactionBuilder::parseRawUnsignedTransaction(wallet->getCurrency(), + tx_bin, 0); + + EXPECT_TRUE(verifyTransactionOutputs(parsedTx, outputs)); + // Values in inputs are missing after parsing. Here we can test only outputs. + + EXPECT_EQ(tx_bin, parsedTx->serialize()); +} + diff --git a/core/test/integration/transactions/transaction_test_helper.h b/core/test/integration/transactions/transaction_test_helper.h index ec1de8b97f..7e1b0175dc 100644 --- a/core/test/integration/transactions/transaction_test_helper.h +++ b/core/test/integration/transactions/transaction_test_helper.h @@ -115,6 +115,9 @@ struct BitcoinMakeBaseTransaction : public BaseFixture { const std::vector& inputs, const std::vector& outputs); + void createAndVerifyTransaction(const std::vector& inputs, + const std::vector& outputs); + std::shared_ptr pool; std::shared_ptr wallet; std::shared_ptr account; From fb6fdd9f968d578795c85a6c6780a0a0ae2709e1 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Wed, 11 May 2022 18:19:52 +0200 Subject: [PATCH 11/24] Fix default value for 'p2trversion' field --- core/src/database/migrations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/database/migrations.cpp b/core/src/database/migrations.cpp index 5f9eb0163d..24d45c29c6 100644 --- a/core/src/database/migrations.cpp +++ b/core/src/database/migrations.cpp @@ -1199,7 +1199,7 @@ namespace ledger { } template <> void migrate<29>(soci::session& sql, api::DatabaseBackendType type) { - sql << "ALTER TABLE bech32_parameters ADD p2trversion VARCHAR(255) DEFAULT 0x01;"; + sql << "ALTER TABLE bech32_parameters ADD p2trversion VARCHAR(255) DEFAULT 01;"; } template <> void rollback<29>(soci::session& sql, api::DatabaseBackendType type) { From 932c8a693e21b9263c1da1bad79dc26ea5d10163 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Tue, 10 May 2022 17:28:18 +0200 Subject: [PATCH 12/24] Remove all actions from appveyor.yml --- appveyor.yml | 144 +-------------------------------------------------- 1 file changed, 2 insertions(+), 142 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 844a4ff0af..a12354e654 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,144 +1,4 @@ -version: 1.0.{build} -image: -- Visual Studio 2017 -environment: - LIB_VERSION: 4.1.3 # Hardcode the LIB_VERSION : should be retrieved by building libcore node module and run tests/lib_version.js - nodejs_version: "9" - appveyor_rdp_password: - secure: jb1LsDmcxCww7tA38S3xSw== - pub_key: - secure: B+4FNFeW6sbRsl7XJYnHUYptAx/2AvV7XySuhhEMdvcloUz0YXxgX/EguKV2Bvm5GaBKYLlq4G2Lw7udm4l68I9g3x+2pZvWiMBUhTRaTGVqi8PXXN+RON62G77yR9Lt/zUR+ljVjuJmRqQBU3NkzOJAAfZ/RDOCAEcWkA3cEE64IkfpqOTHBPW0PpwZcPGFF/l4MGdP8r5XIeu/WwPZHfQjGnDlME0VgkRWflA/Pjt0GcyDSLURXQBk+5JJhn9FjGPCKD+db4xQkW7zhep/su5urEf0HYTmVHRiKMZNfBPGSnL/Lmr6343qc6YF78lyX2U9saoJrf+tuxqAHtUq0KhkHltbNeIUaxvcC7TcH+kEm/Uxl/al8iRiMWE86b8z/wiFQI1xA+oejrFQivbyZVJ+cOifoZ7HhYwP+niOnUG+ZzT5WFLejnsnd4Lwdi5XzWaKKooIPtZ9qcyk15SFzVBayEeeljNk/gByrT6U+25LUqO6ekXrZ+A86Tmjkhp67B5+JNxis+k3sCZ63ASP6LhL9NvrVNky1ZGB8WAZ/hs= - matrix: - - TOOL_CONFIG: vs2017 - BUILD_CONFIG: Debug - CMAKE_GENERATOR: "Visual Studio 15 2017 Win64" - QT5: C:\Qt\5.10.1\msvc2017_64 - SQLITE3_DIR: C:\Tools\vcpkg\packages\sqlite3_x64-windows - CMAKE_DIR: C:\projects\deps\cmake - - TOOL_CONFIG: vs2017 - BUILD_CONFIG: Release - CMAKE_GENERATOR: "Visual Studio 15 2017 Win64" - QT5: C:\Qt\5.10.1\msvc2017_64 - SQLITE3_DIR: C:\Tools\vcpkg\packages\sqlite3_x64-windows - CMAKE_DIR: C:\projects\deps\cmake -build: - verbosity: minimal +build: off -install: - -#Install Sqlite3 -- if not exist "%SQLITE3_DIR%" ( cinst -y sqlite --params '"/InstallDir:%SQLITE3_DIR%"' ) - -#Install cmake -- mkdir C:\projects\deps -- cd C:\projects\deps -- if not exist "%CMAKE_DIR%" ( - set CMAKE_URL="https://github.com/Kitware/CMake/releases/download/v3.16.5/cmake-3.16.5-win64-x64.zip" - appveyor DownloadFile %CMAKE_URL% -FileName cmake.zip - 7z x cmake.zip -oC:\projects\deps > nul - move C:\projects\deps\cmake-* C:\projects\deps\cmake - cmake --version - ) - -#Set SDK -- call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - -#Add Qt5, sqlite3, cmake to PATH -- set PATH=%QT5%\bin;%PATH% -- set PATH=C:\Tools\vcpkg\packages\sqlite3_x64-windows\bin;%PATH% -- set PATH=C:\projects\deps\cmake\bin;%PATH% - -#Create public key -- ps: $fileContent = "-----BEGIN RSA PRIVATE KEY-----`n" -- ps: $fileContent += $env:priv_key.Replace(' ', "`n") -- ps: $fileContent += "`n-----END RSA PRIVATE KEY-----`n" -- ps: Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent -- ssh-keygen -y -f c:\users\appveyor\.ssh\id_rsa > c:\users\appveyor\.ssh\id_rsa.pub - -#Install openssl -- ps: Start-FileDownload 'https://slproweb.com/download/Win64OpenSSL-1_1_1m.exe' -- ps: Start-Process "Win64OpenSSL-1_1_1m.exe" -ArgumentList "/silent /verysilent /sp- /suppressmsgboxes" -Wait - -#Install Node -- ps: Install-Product node $env:nodejs_version - -#Init submodules -- cd C:\projects\lib-ledger-core -- git submodule update --init --recursive - -build_script: - -#Create build directory -- cd C:/projects/ -- mkdir lib-ledger-core-build -- cd lib-ledger-core-build - -# Configuration -- if %BUILD_CONFIG% == Release ( - cmake -G "%CMAKE_GENERATOR%" -DBUILD_TESTS=OFF ../lib-ledger-core - ) else ( - cmake -G "%CMAKE_GENERATOR%" -DBUILD_TESTS=OFF -DSYS_OPENSSL=ON -DOPENSSL_ROOT_DIR=C:\OpenSSL-Win64 -DOPENSSL_USE_STATIC_LIBS=TRUE ../lib-ledger-core - ) - -#Build -- cmake --build . --config %BUILD_CONFIG% -- /m:4 - -- set DLL_NAME=libledger-core.dll -#Naming it ledger-core instead of libledger-core -- set DLL_NAME=ledger-core.dll - -#Copy dll in a lib-ledger-core's child directory: -#Only build directory child is supported for artifacts deployment -- set LIB_DIR=core\src\build -- set LIB_DIR=%LIB_DIR%\%BUILD_CONFIG% - -- set DLL_PATH=%LIB_DIR%\%DLL_NAME% - -- cd ..\lib-ledger-core -- mkdir %LIB_VERSION%\win\%TOOL_CONFIG% - -- if %BUILD_CONFIG% == Release cp "..\lib-ledger-core-build\%DLL_PATH%" ".\%LIB_VERSION%\win\%TOOL_CONFIG%" || echo "FAIL TO COPY DLL" - -#Copy .lib generated by VS (needed for node module) -- set LIBRARY_OBJ_PATH=core\src\%BUILD_CONFIG%\ledger-core.lib -- if %BUILD_CONFIG% == Release cp "..\lib-ledger-core-build\%LIBRARY_OBJ_PATH%" ".\%LIB_VERSION%\win\%TOOL_CONFIG%" || echo "FAIL TO COPY .LIB" -#Copy crypto.dll (also needed for node module) -- set CRYPTO_DLL_PATH=core\lib\openssl\crypto\build\%BUILD_CONFIG%\crypto.dll -- if %BUILD_CONFIG% == Release cp "..\lib-ledger-core-build\%CRYPTO_DLL_PATH%" ".\%LIB_VERSION%\win\%TOOL_CONFIG%" || echo "FAIL TO COPY CRYPTO DLL" - -#Launch tests(To do: fix the tests and enable them in debug) -#- cd ..\lib-ledger-core-build -#- if %BUILD_CONFIG% == Debug ctest -C Debug -VV -#- cd ..\lib-ledger-core -#Build is failing because of configuration -##Create node module -#- cd C:/projects/lib-ledger-core/ledger-core-samples/nodejs -#- npm install -g node-gyp -#- npm install -#- node-gyp --release configure --msvs_version=2015 -#- node-gyp --release build -##- npm install -#- set LIB_VERSION=$(node sample/lib-version.js) -#- echo " >> Get Libcore version %LIB_VERSION%" - -- set COMMIT_HASH=%APPVEYOR_REPO_COMMIT:~0,6% -- if %APPVEYOR_REPO_TAG%==true ( - set "DEPLOY_VERSION=%LIB_VERSION%" - ) else ( - set "DEPLOY_VERSION=%LIB_VERSION%-rc-%COMMIT_HASH%" - ) - -after_build: - - set should_deploy=false - - if %APPVEYOR_REPO_BRANCH%==develop set should_deploy=true - - if %APPVEYOR_REPO_BRANCH%==master set should_deploy=true - - if %APPVEYOR_REPO_TAG%==true set should_deploy=true #for tests - - if %should_deploy%==true aws s3 sync ".\%LIB_VERSION%\win\%TOOL_CONFIG%" "s3://ledger-lib-ledger-core/%DEPLOY_VERSION%/win/%TOOL_CONFIG%" --acl public-read - -artifacts: - - path: .\%LIB_VERSION%\win\%TOOL_CONFIG%\* - name: core_library - -#on_finish: -#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) +deploy: off From d310180624ac8237bf4833a053d6787b276e0296 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Tue, 17 May 2022 14:58:20 +0200 Subject: [PATCH 13/24] Feature flag to manage the generation of P2TR outputs --- core/idl/wallet/configuration.djinni | 3 ++ core/src/api/Configuration.cpp | 2 + core/src/api/Configuration.hpp | 3 ++ .../src/wallet/bitcoin/BitcoinLikeAccount.cpp | 6 ++- .../src/wallet/bitcoin/BitcoinLikeAccount.hpp | 1 + .../BitcoinLikeTransactionBuilder.cpp | 15 +++++- .../BitcoinLikeTransactionBuilder.h | 5 +- .../bitcoin_P2TR_transaction_test.cpp | 52 ++++++++++++++++++- 8 files changed, 81 insertions(+), 6 deletions(-) diff --git a/core/idl/wallet/configuration.djinni b/core/idl/wallet/configuration.djinni index bb35c94f9d..bc298f78ce 100644 --- a/core/idl/wallet/configuration.djinni +++ b/core/idl/wallet/configuration.djinni @@ -118,6 +118,9 @@ Configuration = interface +c { # Grace period (in seconds) during which no "missing" transactions on-chain should trigger # deletion from database const MEMPOOL_GRACE_PERIOD_SECS: string = "MEMPOOL_GRACE_PERIOD_SECS"; + + # Allow the generation of the P2TR (Taproot) outputs + const ALLOW_P2TR: string = "ALLOW_P2TR"; } # Configuration of wallet pools. diff --git a/core/src/api/Configuration.cpp b/core/src/api/Configuration.cpp index 04ce19ddcf..b45d844b95 100644 --- a/core/src/api/Configuration.cpp +++ b/core/src/api/Configuration.cpp @@ -37,4 +37,6 @@ std::string const Configuration::DEACTIVATE_SYNC_TOKEN = {"DEACTIVATE_SYNC_TOKEN std::string const Configuration::MEMPOOL_GRACE_PERIOD_SECS = {"MEMPOOL_GRACE_PERIOD_SECS"}; +std::string const Configuration::ALLOW_P2TR = {"ALLOW_P2TR"}; + } } } // namespace ledger::core::api diff --git a/core/src/api/Configuration.hpp b/core/src/api/Configuration.hpp index 150c887cb9..997ac5c89b 100644 --- a/core/src/api/Configuration.hpp +++ b/core/src/api/Configuration.hpp @@ -70,6 +70,9 @@ class LIBCORE_EXPORT Configuration { * deletion from database */ static std::string const MEMPOOL_GRACE_PERIOD_SECS; + + /** Allow the generation of the P2TR (Taproot) outputs */ + static std::string const ALLOW_P2TR; }; } } } // namespace ledger::core::api diff --git a/core/src/wallet/bitcoin/BitcoinLikeAccount.cpp b/core/src/wallet/bitcoin/BitcoinLikeAccount.cpp index e02df6e769..c420d5dc98 100644 --- a/core/src/wallet/bitcoin/BitcoinLikeAccount.cpp +++ b/core/src/wallet/bitcoin/BitcoinLikeAccount.cpp @@ -394,6 +394,9 @@ namespace ledger { return _keychain; } + inline bool BitcoinLikeAccount::allowP2TR() const { + return getWallet()->getConfig()->getBoolean(api::Configuration::ALLOW_P2TR).value_or(false); + } bool BitcoinLikeAccount::isSynchronizing() { std::lock_guard lock(_synchronizationLock); @@ -818,7 +821,8 @@ namespace ledger { _keychain, lastBlockHeight, logger(), - partial) + partial), + allowP2TR() ); } diff --git a/core/src/wallet/bitcoin/BitcoinLikeAccount.hpp b/core/src/wallet/bitcoin/BitcoinLikeAccount.hpp index d0ab15e78b..104e64cbe0 100644 --- a/core/src/wallet/bitcoin/BitcoinLikeAccount.hpp +++ b/core/src/wallet/bitcoin/BitcoinLikeAccount.hpp @@ -155,6 +155,7 @@ namespace ledger { inline void computeOperationTrust(Operation& operation, const BitcoinLikeBlockchainExplorerTransaction& tx); std::vector> fromBitcoinAddressesToAddresses(const std::vector> &addresses); + inline bool allowP2TR() const; std::shared_ptr _keychain; std::shared_ptr _explorer; diff --git a/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp b/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp index 81d09aaa6d..4d8c1ccea3 100644 --- a/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp +++ b/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.cpp @@ -54,17 +54,20 @@ namespace ledger { _request = cpy._request; _context = cpy._context; _logger = cpy._logger; + _allowP2TR = cpy._allowP2TR; } BitcoinLikeTransactionBuilder::BitcoinLikeTransactionBuilder( const std::shared_ptr &context, const api::Currency ¤cy, const std::shared_ptr &logger, - const BitcoinLikeTransactionBuildFunction &buildFunction) : + const BitcoinLikeTransactionBuildFunction &buildFunction, + bool allowP2TR) : _request(std::make_shared(currency.bitcoinLikeNetworkParameters.value().Dust)) { _currency = currency; _build = buildFunction; _context = context; _logger = logger; + _allowP2TR = allowP2TR; _request.wipe = false; } @@ -173,6 +176,11 @@ namespace ledger { if (a == nullptr) { throw make_exception(api::ErrorCode::RUNTIME_ERROR, "Invalid address {}", address); } + + if (!_allowP2TR && a->isP2TR()) { + throw make_exception(api::ErrorCode::UNSUPPORTED_OPERATION, "Can't send to Taproot address ({}). The ALLOW_P2TR flag is off.", address); + } + BitcoinLikeScript script; // BCH has a fake P2WPKH and P2WSH @@ -185,6 +193,11 @@ namespace ledger { } else if (a->isP2WPKH() || a->isP2WSH()) { script << btccore::OP_0 << a->getHash160(); } else if (a->isP2TR()) { + if (_currency.name != currencies::BITCOIN.name && + _currency.name != currencies::BITCOIN_TESTNET.name && + _currency.name != currencies::BITCOIN_REGTEST.name) { + throw make_exception(api::ErrorCode::UNSUPPORTED_OPERATION, "Can't send to Taproot address ({}) in currency {}", address, _currency.name); + } script << btccore::OP_1 << a->getHash160(); } else { throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Cannot create output script from {}.", address); diff --git a/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.h b/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.h index 6cecd565df..938693ac29 100644 --- a/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.h +++ b/core/src/wallet/bitcoin/transaction_builders/BitcoinLikeTransactionBuilder.h @@ -98,7 +98,8 @@ namespace ledger { const std::shared_ptr& context, const api::Currency& params, const std::shared_ptr& logger, - const BitcoinLikeTransactionBuildFunction& buildFunction); + const BitcoinLikeTransactionBuildFunction& buildFunction, + bool allowP2TR = false); BitcoinLikeTransactionBuilder(const BitcoinLikeTransactionBuilder& cpy); std::shared_ptr addOutput(const std::shared_ptr &amount, @@ -145,7 +146,7 @@ namespace ledger { BitcoinLikeTransactionBuildRequest _request; std::shared_ptr _context; std::shared_ptr _logger; - + bool _allowP2TR = false; }; } } diff --git a/core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp b/core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp index c03457b139..cf7d0787c4 100644 --- a/core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp +++ b/core/test/integration/transactions/bitcoin_P2TR_transaction_test.cpp @@ -33,13 +33,15 @@ #include #include #include "../../fixtures/txes_to_wpkh_fixtures.h" +#include "api/ErrorCode.hpp" using namespace std; struct BitcoinTestNetMakeP2TRTransaction : public BitcoinMakeBaseTransaction { void SetUpConfig() override { testData.configuration = DynamicObject::newInstance(); - testData.configuration->putString(api::Configuration::KEYCHAIN_ENGINE,api::KeychainEngines::BIP173_P2WPKH); + testData.configuration->putString(api::Configuration::KEYCHAIN_ENGINE, api::KeychainEngines::BIP173_P2WPKH); + testData.configuration->putBoolean(api::Configuration::ALLOW_P2TR, true); //https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki testData.configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,"84'/'/'//
"); testData.walletName = randomWalletName(); @@ -93,7 +95,8 @@ TEST_F(BitcoinTestNetMakeP2TRTransaction, CreateP2TRWithOneOutput) { struct BitcoinMainNetMakeP2TRTransaction : public BitcoinMakeBaseTransaction { void SetUpConfig() override { testData.configuration = DynamicObject::newInstance(); - testData.configuration->putString(api::Configuration::KEYCHAIN_ENGINE,api::KeychainEngines::BIP173_P2WPKH); + testData.configuration->putString(api::Configuration::KEYCHAIN_ENGINE, api::KeychainEngines::BIP173_P2WPKH); + testData.configuration->putBoolean(api::Configuration::ALLOW_P2TR, true); //https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki testData.configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,"84'/'/'//
"); testData.walletName = randomWalletName(); @@ -127,3 +130,48 @@ TEST_F(BitcoinMainNetMakeP2TRTransaction, CreateP2TRWithOneOutput) { createAndVerifyTransaction(input_descrs, output_descrs); } +struct BitcoinP2TRFeatureFlagTest : public BitcoinMakeBaseTransaction { + void SetUpConfig() override { + testData.configuration = DynamicObject::newInstance(); + testData.configuration->putString(api::Configuration::KEYCHAIN_ENGINE, api::KeychainEngines::BIP173_P2WPKH); + // Don't set feature flag (false by default): + // testData.configuration->putBoolean(api::Configuration::ALLOW_P2TR, true); + + //https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki + testData.configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,"84'/'/'//
"); + testData.walletName = randomWalletName(); + testData.currencyName = "bitcoin"; + testData.inflate_btc = ledger::testing::txes_to_wpkh::inflate; + } +}; + +TEST_F(BitcoinP2TRFeatureFlagTest, CreateP2TRWithOneOutputFails) { + + std::vector input_descrs = { + { + "673f7e1155dd2cf61c961cedd24608274c0f20cfaeaa1154c2b5ef94ec7b81d1", + 1, + std::make_shared(BigInt(25402)) + } + }; + std::vector output_descrs = { + { + "bc1psf4kpzmrk9aqszdwfmtpqhmvhuhq69frx5efq64yqudq08p3u5vsq8wc5y", + hex::toByteArray("5120826b608b63b17a0809ae4ed6105f6cbf2e0d15233532906aa4071a079c31e519"), + std::make_shared(BigInt(100)) + }, + { + "", // This is a change output. It isn't used for tx building. + hex::toByteArray("00141017b1e1ca8632828f22a4d6c5260f3492b1dd08"), + std::make_shared(BigInt(16396)) + } + }; + + try { + createAndVerifyTransaction(input_descrs, output_descrs); + } catch (const Exception& e) { + std::cerr << "An exception must be thrown here: " << e.what() << std::endl; + EXPECT_EQ(e.getErrorCode(), ledger::core::api::ErrorCode::UNSUPPORTED_OPERATION); + } +} + From 6a0436e0faae1344a39911c0626d598c588df274 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Wed, 25 May 2022 08:25:34 +0200 Subject: [PATCH 14/24] BTCBech32 : protected -> private --- core/src/bitcoin/bech32/BTCBech32.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/bitcoin/bech32/BTCBech32.h b/core/src/bitcoin/bech32/BTCBech32.h index cb9296ba8d..037af5e698 100644 --- a/core/src/bitcoin/bech32/BTCBech32.h +++ b/core/src/bitcoin/bech32/BTCBech32.h @@ -48,7 +48,7 @@ namespace ledger { std::pair, std::vector> decode(const std::string& str) const override; - protected: + private: uint64_t polymod(const std::vector& values) const override; std::vector expandHrp(const std::string& hrp) const override; From 82c3a32f648e0fafb3cf0e679693bc625a5910af Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Wed, 25 May 2022 08:28:11 +0200 Subject: [PATCH 15/24] Create Taproot BitcoinLikeAddress only in Bitcoin --- core/src/bitcoin/BitcoinLikeAddress.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/bitcoin/BitcoinLikeAddress.cpp b/core/src/bitcoin/BitcoinLikeAddress.cpp index d9eb47b7cf..622d586c2b 100644 --- a/core/src/bitcoin/BitcoinLikeAddress.cpp +++ b/core/src/bitcoin/BitcoinLikeAddress.cpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace ledger { namespace core { @@ -214,7 +215,7 @@ namespace ledger { const auto bech32_params = bech32->getBech32Params(); const size_t payload_sz = decoded.second.size(); if (bech32_params.P2WSHVersion == bech32_params.P2WPKHVersion) { - // Bitcoin case (both are equal to zero) => detect keychain engine by length + // Bitcoin-like case (both are equal to zero) => detect keychain engine by length if (decoded.first == bech32_params.P2WSHVersion) { if (payload_sz == 32) { keychainEngine = api::KeychainEngines::BIP173_P2WSH; @@ -224,19 +225,22 @@ namespace ledger { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Unexpected wintess program length"); } } else if (decoded.first == bech32_params.P2TRVersion && payload_sz == 32) { + if (currency.name != currencies::BITCOIN.name && + currency.name != currencies::BITCOIN_TESTNET.name && + currency.name != currencies::BITCOIN_REGTEST.name) { + throw make_exception(api::ErrorCode::UNSUPPORTED_OPERATION, "Can't create Taproot address ({}) in currency {}", address, currency.name); + } keychainEngine = api::KeychainEngines::BIP350_P2TR; } else { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Unknown form of Bech32 format (Bitcoin like)"); } } else { - // Bitcoin Cash case (P2WPKH is 0 and P2WSH is 8), length of hash can be different + // Bitcoin Cash like case (P2WPKH is 0 and P2WSH is 8), length of hash can be different // https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md if (decoded.first == bech32_params.P2WSHVersion && (payload_sz == 32 || payload_sz == 20)) { keychainEngine = api::KeychainEngines::BIP173_P2WSH; } else if (decoded.first == bech32_params.P2WPKHVersion && payload_sz == 20) { keychainEngine = api::KeychainEngines::BIP173_P2WPKH; - } else if (decoded.first == bech32_params.P2TRVersion && payload_sz == 32) { - keychainEngine = api::KeychainEngines::BIP350_P2TR; } else { throw Exception(api::ErrorCode::INVALID_BECH32_FORMAT, "Unknown form of Bech32 format (BCH)"); } From 0120cf16894709d7636a4907558ab1b1aed60759 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Wed, 25 May 2022 08:46:16 +0200 Subject: [PATCH 16/24] Refactor the creation of "bech32_parameters" table --- core/src/database/migrations.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/core/src/database/migrations.cpp b/core/src/database/migrations.cpp index 24d45c29c6..1937b71675 100644 --- a/core/src/database/migrations.cpp +++ b/core/src/database/migrations.cpp @@ -32,6 +32,7 @@ #include "migrations.hpp" #include #include +#include namespace ledger { namespace core { @@ -435,15 +436,21 @@ namespace ledger { sql << "DROP TABLE ripple_currencies"; } + namespace { + std::string getBech32ParamsCreateTableSQL(const char* tableName) { + return fmt::format("CREATE TABLE {}(" + "name VARCHAR(255) PRIMARY KEY NOT NULL REFERENCES bitcoin_currencies(name) ON DELETE CASCADE ON UPDATE CASCADE," + "hrp VARCHAR(255) NOT NULL," + "separator VARCHAR(255) NOT NULL," + "generator VARCHAR(255) NOT NULL," + "p2wpkh_version VARCHAR(255) NOT NULL," + "p2wsh_version VARCHAR(255) NOT NULL" + ")", tableName); + } + } + template <> void migrate<7>(soci::session& sql, api::DatabaseBackendType type) { - sql << "CREATE TABLE bech32_parameters(" - "name VARCHAR(255) PRIMARY KEY NOT NULL REFERENCES bitcoin_currencies(name) ON DELETE CASCADE ON UPDATE CASCADE," - "hrp VARCHAR(255) NOT NULL," - "separator VARCHAR(255) NOT NULL," - "generator VARCHAR(255) NOT NULL," - "p2wpkh_version VARCHAR(255) NOT NULL," - "p2wsh_version VARCHAR(255) NOT NULL" - ")"; + sql << getBech32ParamsCreateTableSQL("bech32_parameters"); } template <> void rollback<7>(soci::session& sql, api::DatabaseBackendType type) { @@ -1207,14 +1214,7 @@ namespace ledger { if (type != api::DatabaseBackendType::SQLITE3) { sql << "ALTER TABLE bech32_parameters DROP p2trversion"; } else { - sql << "CREATE TABLE bech32_parameters_swap(" - "name VARCHAR(255) PRIMARY KEY NOT NULL REFERENCES bitcoin_currencies(name) ON DELETE CASCADE ON UPDATE CASCADE," - "hrp VARCHAR(255) NOT NULL," - "separator VARCHAR(255) NOT NULL," - "generator VARCHAR(255) NOT NULL," - "p2wpkh_version VARCHAR(255) NOT NULL," - "p2wsh_version VARCHAR(255) NOT NULL" - ")"; + sql << getBech32ParamsCreateTableSQL("bech32_parameters_swap"); sql << "INSERT INTO bech32_parameters_swap " "SELECT name, hrp, separator, generator, p2wpkh_version, p2wsh_version " "FROM bech32_parameters"; From 63fbbe63a70a524c38663a94f2597405465544f7 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Mon, 30 May 2022 16:06:53 +0200 Subject: [PATCH 17/24] Add missing } --- core/src/database/migrations.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/database/migrations.cpp b/core/src/database/migrations.cpp index 1b9f05481e..36b23cb455 100644 --- a/core/src/database/migrations.cpp +++ b/core/src/database/migrations.cpp @@ -1227,6 +1227,7 @@ namespace ledger { sql << "DROP INDEX ripple_transactions_hash_index ;"; sql << "DROP INDEX stellar_transactions_hash_index ;"; sql << "DROP INDEX tezos_transactions_hash_index ;"; + } template <> void migrate<30>(soci::session& sql, api::DatabaseBackendType type) { sql << "ALTER TABLE bech32_parameters ADD p2trversion VARCHAR(255) DEFAULT 01;"; From 1da21e4e2d5a3b7079cebd9d0ec7045fa733983b Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Mon, 30 May 2022 16:07:33 +0200 Subject: [PATCH 18/24] CURRENT_DATABASE_SCHEME_VERSION: 29 -> 30 (after merge from main) --- core/src/database/DatabaseSessionPool.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/database/DatabaseSessionPool.hpp b/core/src/database/DatabaseSessionPool.hpp index 15322e6ce5..c5c6af29bc 100644 --- a/core/src/database/DatabaseSessionPool.hpp +++ b/core/src/database/DatabaseSessionPool.hpp @@ -61,7 +61,7 @@ namespace ledger { const std::string &password = "" ); - static const int CURRENT_DATABASE_SCHEME_VERSION = 29; + static const int CURRENT_DATABASE_SCHEME_VERSION = 30; void performDatabaseMigration(); void performDatabaseRollback(); From e873cf3dbae8c86732f5a4aeeea071e4fd483e0c Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Mon, 30 May 2022 22:19:34 +0200 Subject: [PATCH 19/24] Update version: 4.1.14 -> 4.2.0 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 409c16f473..27002516a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,8 +43,8 @@ include_what_you_use() # add cmake conf option IWYU=ON to activate # The project version number. set(VERSION_MAJOR 4 CACHE STRING "Project major version number.") -set(VERSION_MINOR 1 CACHE STRING "Project minor version number.") -set(VERSION_PATCH 14 CACHE STRING "Project patch version number.") +set(VERSION_MINOR 2 CACHE STRING "Project minor version number.") +set(VERSION_PATCH 0 CACHE STRING "Project patch version number.") mark_as_advanced(VERSION_MAJOR VERSION_MINOR VERSION_PATCH) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY build) From 1ea50110ab360fe97667fa22df28a3267dcfb64d Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Tue, 31 May 2022 10:53:59 +0200 Subject: [PATCH 20/24] Update version: 4.2.0 -> 4.3.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 27002516a5..e1ab4f91d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,7 @@ include_what_you_use() # add cmake conf option IWYU=ON to activate # The project version number. set(VERSION_MAJOR 4 CACHE STRING "Project major version number.") -set(VERSION_MINOR 2 CACHE STRING "Project minor version number.") +set(VERSION_MINOR 3 CACHE STRING "Project minor version number.") set(VERSION_PATCH 0 CACHE STRING "Project patch version number.") mark_as_advanced(VERSION_MAJOR VERSION_MINOR VERSION_PATCH) From bf3715dbb1338267de829d9d4ea20448e72b5421 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Tue, 31 May 2022 17:29:28 +0200 Subject: [PATCH 21/24] Remove "branches" restriction on CricleCI --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cfc4bbf0ab..5f5659432c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -383,8 +383,8 @@ workflows: filters: tags: only: /.*/ - branches: - only: /^(?!pull\/).*$/ + #branches: + # only: /^(?!pull\/).*$/ - build_android_release: <<: *default_context filters: @@ -409,8 +409,8 @@ workflows: filters: tags: only: /.*/ - branches: - only: /^(?!pull\/).*$/ + #branches: + # only: /^(?!pull\/).*$/ - build_linux_debug: <<: *default_context - build_macos_debug: From 3f95246bdaf811517846636116392d62f4cad8f3 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Thu, 16 Jun 2022 15:30:01 +0200 Subject: [PATCH 22/24] Fix generate_doc pipeline --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f5659432c..2086ebff90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -345,8 +345,9 @@ jobs: command: | sudo apt-get update -q=2 sudo apt-get install -qy doxygen rsync software-properties-common - sudo add-apt-repository --update "deb http://httpredir.debian.org/debian stretch-backports main" - sudo apt-get install -qy -t stretch-backports cmake + wget https://cmake.org/files/v3.16/cmake-3.16.9.tar.gz + tar -xvf cmake-3.16.9.tar.gz + cd cmake-3.16.9 && ./bootstrap && make && sudo make install - run: cmake -H. -Bbuild - run: command: make doc From 35b2eac0b24118db59107b7b6a0de79a77b43705 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Mon, 20 Jun 2022 15:19:36 +0200 Subject: [PATCH 23/24] Rollback CircleCI releasing logic --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2086ebff90..422bef6f2f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -384,8 +384,8 @@ workflows: filters: tags: only: /.*/ - #branches: - # only: /^(?!pull\/).*$/ + branches: + only: /^(?!pull\/).*$/ - build_android_release: <<: *default_context filters: @@ -410,8 +410,8 @@ workflows: filters: tags: only: /.*/ - #branches: - # only: /^(?!pull\/).*$/ + branches: + only: /^(?!pull\/).*$/ - build_linux_debug: <<: *default_context - build_macos_debug: From f7390571b16bb78b296f6e2c6c495f860deb8622 Mon Sep 17 00:00:00 2001 From: Viktor Bocharov <94441294+viktorb-ledger@users.noreply.github.com> Date: Mon, 20 Jun 2022 15:43:20 +0200 Subject: [PATCH 24/24] Update CHANGELOG.md --- CHANGELOG.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eca2856b0..141479dc30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ +## 4.3.0 + +- Send to Taproot addresses (Bitcoin only): +-- Bech32m encoding +-- Generation of P2TR outputs +-- ALLOW\_P2TR feature flag (off by default) blocks "Send to Taproot" + +Side effect: can't serialize native segwit address to base58 and legacy address to bech32. + ## 4.1.2 - Fix of Tezos fees calculation formula - + ## 4.1.1 > Unreleased @@ -23,11 +32,11 @@ - Performance improvements - Tezos: support of all tz adresses (tz1, tz2, tz3) by implementing ED25519, SECP256K1 and P256 -- Tezos: support of grouped transaction messages +- Tezos: support of grouped transaction messages - Tezos: fix smart contracts balance - Tezos: concurent transactions (optimistic counter) - Tezos: get current delegate -- Tezos: fees estimation +- Tezos: fees estimation - Tezos: get token balance ## 4.0.6