From 3c387d3418370270a56547542e88bb4c21660c11 Mon Sep 17 00:00:00 2001 From: gogoex <110195520+gogoex@users.noreply.github.com> Date: Sun, 10 Dec 2023 17:52:47 +0900 Subject: [PATCH 1/8] add double public key support to DestinationEncoder and DecodeDestination and remove base58 double public key support --- src/bech32_mod.cpp | 7 +--- src/bech32_mod.h | 7 ++++ src/blsct/arith/mcl/mcl_g1point.cpp | 4 +-- src/blsct/double_public_key.cpp | 15 ++++++--- src/blsct/double_public_key.h | 2 +- src/kernel/chainparams.cpp | 8 ++--- src/kernel/chainparams.h | 3 +- src/key_io.cpp | 51 ++++++++++++++++++----------- src/test/bech32_mod_tests.cpp | 4 +-- 9 files changed, 61 insertions(+), 40 deletions(-) diff --git a/src/bech32_mod.cpp b/src/bech32_mod.cpp index ad52acdaeb533..da0bc04189308 100644 --- a/src/bech32_mod.cpp +++ b/src/bech32_mod.cpp @@ -211,12 +211,7 @@ DecodeResult Decode(const std::string& str) { if (!CheckCharacters(str, errors)) return {}; size_t pos = str.rfind('1'); - // double public key bech32 string is 165-byte long and consists of: - // - 2-byte hrp - // - 1-byte separator '1' - // - 154-byte key data (96 bytes / 5 bits = 153.6) - // - 8-byte checksum - if (str.size() != 165 // double public key should be encoded to 165-byte bech32 string + if (str.size() != DOUBLE_PUBKEY_ENC_SIZE || pos == str.npos // separator '1' should be included || pos == 0 // hrp part should not be empty || pos + 9 > str.size() // data part should not be empty diff --git a/src/bech32_mod.h b/src/bech32_mod.h index 56f5a1be203d7..26eaada340d50 100644 --- a/src/bech32_mod.h +++ b/src/bech32_mod.h @@ -21,6 +21,13 @@ namespace bech32_mod { +// double public key after encoding to bech32_mod is 165-byte long consisting of: +// - 2-byte hrp +// - 1-byte separator '1' +// - 154-byte key data (96 bytes / 5 bits = 153.6) +// - 8-byte checksum +constexpr size_t DOUBLE_PUBKEY_ENC_SIZE = 2 + 1 + 154 + 8; + enum class Encoding { INVALID, //!< Failed decoding diff --git a/src/blsct/arith/mcl/mcl_g1point.cpp b/src/blsct/arith/mcl/mcl_g1point.cpp index 93fd1b9de9972..fde515e08775a 100644 --- a/src/blsct/arith/mcl/mcl_g1point.cpp +++ b/src/blsct/arith/mcl/mcl_g1point.cpp @@ -185,9 +185,7 @@ std::vector MclG1Point::GetVch() const bool MclG1Point::SetVch(const std::vector& b) { if (mclBnG1_deserialize(&m_point, &b[0], b.size()) == 0) { - mclBnG1 x; - mclBnG1_clear(&x); - m_point = x; + mclBnG1_clear(&m_point); return false; } return true; diff --git a/src/blsct/double_public_key.cpp b/src/blsct/double_public_key.cpp index 79be02d75b8d2..9311465802b3d 100644 --- a/src/blsct/double_public_key.cpp +++ b/src/blsct/double_public_key.cpp @@ -3,16 +3,23 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include namespace blsct { DoublePublicKey::DoublePublicKey(const std::vector& keys) { if (keys.size() != SIZE) return; - std::vector vkData(SIZE / 2); - std::vector skData(SIZE / 2); - std::copy(keys.begin(), keys.begin() + SIZE / 2, vkData.begin()); - std::copy(keys.begin() + SIZE / 2, keys.end(), skData.begin()); + + std::vector vkData(blsct::PublicKey::SIZE); + std::vector skData(blsct::PublicKey::SIZE); + std::copy(keys.begin(), keys.begin() + blsct::PublicKey::SIZE, vkData.begin()); + std::copy(keys.begin() + blsct::PublicKey::SIZE, keys.end(), skData.begin()); + + // check vkData and skData are valid serialization of points + MclG1Point p; + if (!p.SetVch(vkData) || !p.SetVch(skData)) return; + vk = vkData; sk = skData; } diff --git a/src/blsct/double_public_key.h b/src/blsct/double_public_key.h index 3dd5d3b74bf4d..7e77b28789871 100644 --- a/src/blsct/double_public_key.h +++ b/src/blsct/double_public_key.h @@ -21,7 +21,7 @@ class DoublePublicKey PublicKey sk; public: - static constexpr size_t SIZE = 48 * 2; + static constexpr size_t SIZE = blsct::PublicKey::SIZE * 2; DoublePublicKey() {} DoublePublicKey(const PublicKey& vk_, const PublicKey& sk_) : vk(vk_), sk(sk_) {} diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 77069e802fd49..0d916bbdf944e 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -141,13 +141,13 @@ class CMainParams : public CChainParams { vSeeds.emplace_back("seed.bitcoin.wiz.biz."); // Jason Maurice base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,0); - base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,5); base58Prefixes[SECRET_KEY] = std::vector(1,128); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4}; bech32_hrp = "bc"; + bech32_mod_hrp = "nv"; vFixedSeeds = std::vector(std::begin(chainparams_seed_main), std::end(chainparams_seed_main)); @@ -250,13 +250,13 @@ class CTestNetParams : public CChainParams { vSeeds.emplace_back("testnet-seed.bluematt.me."); // Just a static list of stable node(s), only supports x9 base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); - base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "tb"; + bech32_mod_hrp = "tn"; vFixedSeeds = std::vector(std::begin(chainparams_seed_test), std::end(chainparams_seed_test)); @@ -376,13 +376,13 @@ class SigNetParams : public CChainParams { vFixedSeeds.clear(); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); - base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "tb"; + bech32_mod_hrp = "tn"; fDefaultConsistencyChecks = false; fRequireStandard = true; @@ -505,13 +505,13 @@ class CRegTestParams : public CChainParams }; base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); - base58Prefixes[BLSCT_ADDRESS] = {73,33}; base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "bcrt"; + bech32_mod_hrp = "tn"; } }; diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h index a0fc84393a69f..7145921052a58 100644 --- a/src/kernel/chainparams.h +++ b/src/kernel/chainparams.h @@ -82,7 +82,6 @@ class CChainParams SECRET_KEY, EXT_PUBLIC_KEY, EXT_SECRET_KEY, - BLSCT_ADDRESS, MAX_BASE58_TYPES }; @@ -124,6 +123,7 @@ class CChainParams const std::vector& DNSSeeds() const { return vSeeds; } const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::string& Bech32HRP() const { return bech32_hrp; } + const std::string& Bech32ModHRP() const { return bech32_mod_hrp; } const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } @@ -176,6 +176,7 @@ class CChainParams std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; std::string bech32_hrp; + std::string bech32_mod_hrp; ChainType m_chain_type; CBlock genesis; std::vector vFixedSeeds; diff --git a/src/key_io.cpp b/src/key_io.cpp index 82a794c891e60..e8eefe7ebd15e 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -2,10 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include - #include #include +#include +#include +#include +#include +#include #include #include @@ -26,12 +29,12 @@ class DestinationEncoder std::string operator()(const blsct::DoublePublicKey& id) const { - std::vector data = m_params.Base58Prefix(CChainParams::BLSCT_ADDRESS); - auto vchView = id.GetVkVch(); - auto vchSpend = id.GetSkVch(); - data.insert(data.end(), vchView.begin(), vchView.end()); - data.insert(data.end(), vchSpend.begin(), vchSpend.end()); - return EncodeBase58Check(data); + std::vector dpk_v8 = id.GetVch(); + std::vector dpk_v5; + dpk_v5.reserve(bech32_mod::DOUBLE_PUBKEY_ENC_SIZE); + ConvertBits<8, 5, true>([&](uint8_t c) { dpk_v5.push_back(c); }, dpk_v8.begin(), dpk_v8.end()); + + return bech32_mod::Encode(bech32_mod::Encoding::BECH32, m_params.Bech32ModHRP(), dpk_v5); } std::string operator()(const PKHash& id) const @@ -92,6 +95,26 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par uint160 hash; error_str = ""; + // if double public key + if (str.size() == bech32_mod::DOUBLE_PUBKEY_ENC_SIZE + && ToLower(str.substr(0, params.Bech32ModHRP().size())) == params.Bech32ModHRP() + && str[params.Bech32ModHRP().size()] == '1' + ) { + const auto dec = bech32_mod::Decode(str); + if ((dec.encoding == bech32_mod::Encoding::BECH32 || dec.encoding == bech32_mod::Encoding::BECH32M) && dec.data.size() > 0) { + // The data part consists of two concatenated 48-byte public keys + data.reserve(blsct::DoublePublicKey::SIZE); + if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin(), dec.data.end())) { + return CNoDestination(); + } + + auto dpk = blsct::DoublePublicKey(data); + return dpk.IsValid() ? CTxDestination(dpk) : CNoDestination(); + } + return CNoDestination(); + } + + data.clear(); // Note this will be false if it is a valid Bech32 address for a different network bool is_bech32 = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP()); @@ -123,17 +146,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par } return CNoDestination(); } else if (!is_bech32) { - // Try Base58 decoding without the checksum, using a much larger max length - if (DecodeBase58Check(str, data, 100)) { - // base58-encoded BLSCT addresses. - const std::vector& blsct_prefix = params.Base58Prefix(CChainParams::BLSCT_ADDRESS); - std::vector blsctKeysData; - blsctKeysData.resize(2*blsct::PublicKey::SIZE); - if (data.size() == blsctKeysData.size() + blsct_prefix.size() && std::equal(blsct_prefix.begin(), blsct_prefix.end(), data.begin())) { - std::copy(data.begin() + blsct_prefix.size(), data.end(), blsctKeysData.begin()); - return blsct::DoublePublicKey(blsctKeysData); - } - } if (!DecodeBase58(str, data, 100)) { + if (!DecodeBase58(str, data, 100)) { error_str = "Invalid or unsupported Segwit (Bech32) or Base58 encoding."; } else { error_str = "Invalid checksum or length of Base58 address (P2PKH or P2SH)"; diff --git a/src/test/bech32_mod_tests.cpp b/src/test/bech32_mod_tests.cpp index 0d3c99f583cf9..73b73d34995fb 100644 --- a/src/test/bech32_mod_tests.cpp +++ b/src/test/bech32_mod_tests.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "validationinterface.h" #include #include #include @@ -76,7 +77,6 @@ size_t test_error_detection( const size_t num_tests, const bool expect_errors ) { - std::string hrp = "nv"; size_t unexpected_results = 0; for (size_t i=0; i dpk_v5; ConvertBits<8, 5, true>([&](uint8_t c) { dpk_v5.push_back(c); }, dpk_v8.begin(), dpk_v8.end()); - auto dpk_bech32 = bech32_mod::Encode(encoding, hrp, dpk_v5); + auto dpk_bech32 = bech32_mod::Encode(encoding, "nv", dpk_v5); embed_errors(dpk_bech32, num_errors); auto res = bech32_mod::Decode(dpk_bech32); From 608aa385efc0cd17d1f1d923d7b1083f591acdde Mon Sep 17 00:00:00 2001 From: gogoex <110195520+gogoex@users.noreply.github.com> Date: Tue, 12 Dec 2023 14:31:04 +0900 Subject: [PATCH 2/8] covert bit-5 to bit-8 and vice versa in bech32_mod. add a flag to indicate if the object is fully constructed and adds tests for it --- src/bech32_mod.cpp | 44 +++++++++++++++++++++++++++++++++ src/bech32_mod.h | 17 +++++++++++++ src/blsct/double_public_key.cpp | 3 ++- src/blsct/double_public_key.h | 15 ++++++++--- src/key_io.cpp | 35 +++++++++----------------- src/test/blsct/keys_tests.cpp | 43 ++++++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 28 deletions(-) diff --git a/src/bech32_mod.cpp b/src/bech32_mod.cpp index da0bc04189308..3ad50ffc37480 100644 --- a/src/bech32_mod.cpp +++ b/src/bech32_mod.cpp @@ -237,5 +237,49 @@ DecodeResult Decode(const std::string& str) { return {result, std::move(hrp), data(values.begin(), values.end() - 8)}; } +std::string EncodeDoublePublicKey( + const CChainParams& params, + const Encoding encoding, + const blsct::DoublePublicKey& dpk +) { + std::vector dpk_v8 = dpk.GetVch(); + std::vector dpk_v5; + dpk_v5.reserve(DOUBLE_PUBKEY_ENC_SIZE); + + // ignoring the return value since this conversion always succeeds + ConvertBits<8, 5, true>([&](uint8_t c) { dpk_v5.push_back(c); }, dpk_v8.begin(), dpk_v8.end()); + + return Encode(encoding, params.Bech32ModHRP(), dpk_v5); +} + +bool DecodeDoublePublicKey( + const CChainParams& params, + const std::string& str, + std::vector& data +) { + const auto hrp = ToLower(str.substr(0, params.Bech32ModHRP().size())); + + // str needs to be of the expected length and have the expected hrp + if (str.size() != bech32_mod::DOUBLE_PUBKEY_ENC_SIZE + || hrp != params.Bech32ModHRP() + || str[params.Bech32ModHRP().size()] != '1' + ) return false; + + // decode to 5-bit based byte vector + const auto dec = bech32_mod::Decode(str); + + // check if it has expected encoding and the data is of the expected length + if ((dec.encoding != bech32_mod::Encoding::BECH32 && dec.encoding != bech32_mod::Encoding::BECH32M) + || dec.data.size() != 154 + ) return false; + + // The data part consists of two concatenated 48-byte public keys + data.reserve(blsct::DoublePublicKey::SIZE); + if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin(), dec.data.end())) { + return false; + } + return true; +} + } // namespace bech32_mod diff --git a/src/bech32_mod.h b/src/bech32_mod.h index 26eaada340d50..474b46342cd4f 100644 --- a/src/bech32_mod.h +++ b/src/bech32_mod.h @@ -14,6 +14,8 @@ #ifndef BITCOIN_BECH32_MOD_H #define BITCOIN_BECH32_MOD_H +#include +#include #include #include #include @@ -52,6 +54,21 @@ struct DecodeResult /** Decode a Bech32 or Bech32m string. */ DecodeResult Decode(const std::string& str); +/** Encode DoublePublicKey to Bech32 or Bech32m string. Encoding must be one of BECH32 or BECH32M. */ +std::string EncodeDoublePublicKey( + const CChainParams& params, + const Encoding encoding, + const blsct::DoublePublicKey& dpk +); + +/** Decode a Bech32 or Bech32m string to concatination of two serialized public keys + * Overwrite given data with the concatenated public keys if succeeded. */ +bool DecodeDoublePublicKey( + const CChainParams& params, + const std::string& str, + std::vector& data +); + } // namespace bech32_mod #endif // BITCOIN_BECH32_MOD_H diff --git a/src/blsct/double_public_key.cpp b/src/blsct/double_public_key.cpp index 9311465802b3d..4bf3840d978e6 100644 --- a/src/blsct/double_public_key.cpp +++ b/src/blsct/double_public_key.cpp @@ -22,6 +22,7 @@ DoublePublicKey::DoublePublicKey(const std::vector& keys) vk = vkData; sk = skData; + is_fully_built = true; } CKeyID DoublePublicKey::GetID() const @@ -76,7 +77,7 @@ bool DoublePublicKey::operator<(const DoublePublicKey& rhs) const bool DoublePublicKey::IsValid() const { - return vk.IsValid() && sk.IsValid(); + return is_fully_built && vk.IsValid() && sk.IsValid(); } std::vector DoublePublicKey::GetVkVch() const diff --git a/src/blsct/double_public_key.h b/src/blsct/double_public_key.h index 7e77b28789871..ede7d9ff0b3e5 100644 --- a/src/blsct/double_public_key.h +++ b/src/blsct/double_public_key.h @@ -19,14 +19,21 @@ class DoublePublicKey PublicKey vk; PublicKey sk; + bool is_fully_built = false; public: static constexpr size_t SIZE = blsct::PublicKey::SIZE * 2; - DoublePublicKey() {} - DoublePublicKey(const PublicKey& vk_, const PublicKey& sk_) : vk(vk_), sk(sk_) {} - DoublePublicKey(const Point& vk_, const Point& sk_) : vk(vk_), sk(sk_) {} - DoublePublicKey(const std::vector& vk_, const std::vector& sk_) : vk(vk_), sk(sk_) {} + DoublePublicKey() : is_fully_built(true) {} + DoublePublicKey(const PublicKey& vk_, const PublicKey& sk_) : vk(vk_), sk(sk_), is_fully_built(true) {} + DoublePublicKey(const Point& vk_, const Point& sk_) : vk(vk_), sk(sk_), is_fully_built(true) {} + + DoublePublicKey(const std::vector& vk_, const std::vector& sk_) : vk(vk_), sk(sk_) + { + MclG1Point p; + is_fully_built = p.SetVch(vk_) && p.SetVch(sk_); + } + DoublePublicKey(const std::vector& keys); SERIALIZE_METHODS(DoublePublicKey, obj) { READWRITE(obj.vk, obj.sk); } diff --git a/src/key_io.cpp b/src/key_io.cpp index e8eefe7ebd15e..5c5c78702f414 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -29,12 +29,11 @@ class DestinationEncoder std::string operator()(const blsct::DoublePublicKey& id) const { - std::vector dpk_v8 = id.GetVch(); - std::vector dpk_v5; - dpk_v5.reserve(bech32_mod::DOUBLE_PUBKEY_ENC_SIZE); - ConvertBits<8, 5, true>([&](uint8_t c) { dpk_v5.push_back(c); }, dpk_v8.begin(), dpk_v8.end()); - - return bech32_mod::Encode(bech32_mod::Encoding::BECH32, m_params.Bech32ModHRP(), dpk_v5); + return bech32_mod::EncodeDoublePublicKey( + m_params, + bech32_mod::Encoding::BECH32M, + id + ); } std::string operator()(const PKHash& id) const @@ -95,26 +94,16 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par uint160 hash; error_str = ""; - // if double public key - if (str.size() == bech32_mod::DOUBLE_PUBKEY_ENC_SIZE - && ToLower(str.substr(0, params.Bech32ModHRP().size())) == params.Bech32ModHRP() - && str[params.Bech32ModHRP().size()] == '1' - ) { - const auto dec = bech32_mod::Decode(str); - if ((dec.encoding == bech32_mod::Encoding::BECH32 || dec.encoding == bech32_mod::Encoding::BECH32M) && dec.data.size() > 0) { - // The data part consists of two concatenated 48-byte public keys - data.reserve(blsct::DoublePublicKey::SIZE); - if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin(), dec.data.end())) { - return CNoDestination(); - } - - auto dpk = blsct::DoublePublicKey(data); - return dpk.IsValid() ? CTxDestination(dpk) : CNoDestination(); + // first try to decode str as a double public key + if (bech32_mod::DecodeDoublePublicKey(params, str, data)) { + auto dpk = blsct::DoublePublicKey(data); + if (dpk.IsValid()) { + return CTxDestination(dpk); } - return CNoDestination(); + // if invalid, try other types of destinations } - data.clear(); + // Note this will be false if it is a valid Bech32 address for a different network bool is_bech32 = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP()); diff --git a/src/test/blsct/keys_tests.cpp b/src/test/blsct/keys_tests.cpp index 613ca55be4f71..5111b7e8ddda0 100644 --- a/src/test/blsct/keys_tests.cpp +++ b/src/test/blsct/keys_tests.cpp @@ -200,4 +200,47 @@ BOOST_AUTO_TEST_CASE(aggretate_empty_public_keys) BOOST_CHECK_THROW(pks.Aggregate(), std::runtime_error); } +BOOST_AUTO_TEST_CASE(double_public_key_with_bad_input_vector) +{ + { + // empty input vector + std::vector keys; + blsct::DoublePublicKey dpk(keys); + BOOST_CHECK(dpk.IsValid() == false); + } + { + // input vector of invalid shorter size + auto g = MclG1Point::GetBasePoint(); + auto keys = g.GetVch(); + + // drop the last element + keys.pop_back(); + + blsct::DoublePublicKey dpk(keys); + BOOST_CHECK(dpk.IsValid() == false); + } + { + // input vector of invalid larger size + auto g = MclG1Point::GetBasePoint(); + auto keys = g.GetVch(); + + // append an element + keys.push_back(1); + + blsct::DoublePublicKey dpk(keys); + BOOST_CHECK(dpk.IsValid() == false); + } + { + // input vector of valid size with bad content + auto g = MclG1Point::GetBasePoint(); + auto keys = g.GetVch(); + + // alter keys[0] from 151 to 152 + keys[0] = keys[0] + 1; + + blsct::DoublePublicKey dpk(keys); + BOOST_CHECK(dpk.IsValid() == false); + } +} + BOOST_AUTO_TEST_SUITE_END() From e3bb27e3db46d4be97480b8968ab16952f39c890 Mon Sep 17 00:00:00 2001 From: gogoex <110195520+gogoex@users.noreply.github.com> Date: Tue, 12 Dec 2023 14:37:39 +0900 Subject: [PATCH 3/8] fix typo --- src/bech32_mod.h | 2 +- src/test/bech32_mod_tests.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bech32_mod.h b/src/bech32_mod.h index 474b46342cd4f..cb6bc6fb40b4e 100644 --- a/src/bech32_mod.h +++ b/src/bech32_mod.h @@ -61,7 +61,7 @@ std::string EncodeDoublePublicKey( const blsct::DoublePublicKey& dpk ); -/** Decode a Bech32 or Bech32m string to concatination of two serialized public keys +/** Decode a Bech32 or Bech32m string to concatenation of two serialized public keys * Overwrite given data with the concatenated public keys if succeeded. */ bool DecodeDoublePublicKey( const CChainParams& params, diff --git a/src/test/bech32_mod_tests.cpp b/src/test/bech32_mod_tests.cpp index 73b73d34995fb..d7898f77e3af0 100644 --- a/src/test/bech32_mod_tests.cpp +++ b/src/test/bech32_mod_tests.cpp @@ -3,7 +3,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "validationinterface.h" #include #include #include From e72b92818ac747d8b407d94ca0e5a259e16885b8 Mon Sep 17 00:00:00 2001 From: gogoex <110195520+gogoex@users.noreply.github.com> Date: Tue, 12 Dec 2023 16:46:07 +0900 Subject: [PATCH 4/8] directly return DoublePublicKey from DecodeDoublePublicKey --- src/bech32_mod.cpp | 21 ++++++++++++++------- src/bech32_mod.h | 9 ++++----- src/key_io.cpp | 16 ++++++++-------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/bech32_mod.cpp b/src/bech32_mod.cpp index 3ad50ffc37480..ed829015f9594 100644 --- a/src/bech32_mod.cpp +++ b/src/bech32_mod.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "blsct/double_public_key.h" #include #include @@ -252,10 +253,9 @@ std::string EncodeDoublePublicKey( return Encode(encoding, params.Bech32ModHRP(), dpk_v5); } -bool DecodeDoublePublicKey( +std::optional DecodeDoublePublicKey( const CChainParams& params, - const std::string& str, - std::vector& data + const std::string& str ) { const auto hrp = ToLower(str.substr(0, params.Bech32ModHRP().size())); @@ -263,7 +263,7 @@ bool DecodeDoublePublicKey( if (str.size() != bech32_mod::DOUBLE_PUBKEY_ENC_SIZE || hrp != params.Bech32ModHRP() || str[params.Bech32ModHRP().size()] != '1' - ) return false; + ) return std::nullopt; // decode to 5-bit based byte vector const auto dec = bech32_mod::Decode(str); @@ -271,14 +271,21 @@ bool DecodeDoublePublicKey( // check if it has expected encoding and the data is of the expected length if ((dec.encoding != bech32_mod::Encoding::BECH32 && dec.encoding != bech32_mod::Encoding::BECH32M) || dec.data.size() != 154 - ) return false; + ) return std::nullopt; // The data part consists of two concatenated 48-byte public keys + std::vector data; data.reserve(blsct::DoublePublicKey::SIZE); if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin(), dec.data.end())) { - return false; + return std::nullopt; + } + + blsct::DoublePublicKey dpk(data); + if (dpk.IsValid()) { + return dpk; + } else { + return std::nullopt; } - return true; } } // namespace bech32_mod diff --git a/src/bech32_mod.h b/src/bech32_mod.h index cb6bc6fb40b4e..a8a31672f44fc 100644 --- a/src/bech32_mod.h +++ b/src/bech32_mod.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -61,12 +62,10 @@ std::string EncodeDoublePublicKey( const blsct::DoublePublicKey& dpk ); -/** Decode a Bech32 or Bech32m string to concatenation of two serialized public keys - * Overwrite given data with the concatenated public keys if succeeded. */ -bool DecodeDoublePublicKey( +/** Decode a Bech32 or Bech32m string to a DoublePublicKey. */ +std::optional DecodeDoublePublicKey( const CChainParams& params, - const std::string& str, - std::vector& data + const std::string& str ); } // namespace bech32_mod diff --git a/src/key_io.cpp b/src/key_io.cpp index 5c5c78702f414..39922035e55c3 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -90,19 +90,19 @@ class DestinationEncoder CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str, std::vector* error_locations) { - std::vector data; - uint160 hash; - error_str = ""; - - // first try to decode str as a double public key - if (bech32_mod::DecodeDoublePublicKey(params, str, data)) { - auto dpk = blsct::DoublePublicKey(data); + // first try to decode str to a double public key + auto maybe_dpk = bech32_mod::DecodeDoublePublicKey(params, str); + if (maybe_dpk) { + auto dpk = maybe_dpk.value(); if (dpk.IsValid()) { return CTxDestination(dpk); } // if invalid, try other types of destinations } - data.clear(); + + std::vector data; + uint160 hash; + error_str = ""; // Note this will be false if it is a valid Bech32 address for a different network bool is_bech32 = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP()); From 786b0a6a1c22f7a69e987a796de7090ae5b76c37 Mon Sep 17 00:00:00 2001 From: gogoex <110195520+gogoex@users.noreply.github.com> Date: Thu, 14 Dec 2023 07:43:41 +0900 Subject: [PATCH 5/8] move Encode/DecodeDoublePublicKey to key_io.cpp --- src/bech32_mod.cpp | 64 ++++------------------------------- src/bech32_mod.h | 22 ++---------- src/key_io.cpp | 54 +++++++++++++++++++++++++++-- src/key_io.h | 21 ++++++++++++ src/test/bech32_mod_tests.cpp | 17 ++++------ src/test/key_io_tests.cpp | 28 +++++++++++---- 6 files changed, 109 insertions(+), 97 deletions(-) diff --git a/src/bech32_mod.cpp b/src/bech32_mod.cpp index ed829015f9594..f6921ee9f686a 100644 --- a/src/bech32_mod.cpp +++ b/src/bech32_mod.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace bech32_mod { @@ -192,6 +193,9 @@ data CreateChecksum(Encoding encoding, const std::string& hrp, const data& value /** Encode a Bech32 or Bech32m string. */ std::string Encode(Encoding encoding, const std::string& hrp, const data& values) { + if (values.size() != DOUBLE_PUBKEY_DATA_ENC_SIZE) { + throw std::runtime_error("Expected values to be a double public key"); + } // First ensure that the HRP is all lowercase. BIP-173 and BIP350 require an encoder // to return a lowercase Bech32/Bech32m string, but if given an uppercase HRP, the // result will always be invalid. @@ -206,19 +210,13 @@ std::string Encode(Encoding encoding, const std::string& hrp, const data& values return ret; } -/** Decode a Bech32 or Bech32m string. */ +/** Decode a Bech32 or Bech32m string. Expects + * str to be a valid encoding of DoublePublicKey */ DecodeResult Decode(const std::string& str) { std::vector errors; if (!CheckCharacters(str, errors)) return {}; size_t pos = str.rfind('1'); - if (str.size() != DOUBLE_PUBKEY_ENC_SIZE - || pos == str.npos // separator '1' should be included - || pos == 0 // hrp part should not be empty - || pos + 9 > str.size() // data part should not be empty - ) { - return {}; - } data values(str.size() - 1 - pos); for (size_t i = 0; i < str.size() - 1 - pos; ++i) { unsigned char c = str[i + pos + 1]; @@ -238,55 +236,5 @@ DecodeResult Decode(const std::string& str) { return {result, std::move(hrp), data(values.begin(), values.end() - 8)}; } -std::string EncodeDoublePublicKey( - const CChainParams& params, - const Encoding encoding, - const blsct::DoublePublicKey& dpk -) { - std::vector dpk_v8 = dpk.GetVch(); - std::vector dpk_v5; - dpk_v5.reserve(DOUBLE_PUBKEY_ENC_SIZE); - - // ignoring the return value since this conversion always succeeds - ConvertBits<8, 5, true>([&](uint8_t c) { dpk_v5.push_back(c); }, dpk_v8.begin(), dpk_v8.end()); - - return Encode(encoding, params.Bech32ModHRP(), dpk_v5); -} - -std::optional DecodeDoublePublicKey( - const CChainParams& params, - const std::string& str -) { - const auto hrp = ToLower(str.substr(0, params.Bech32ModHRP().size())); - - // str needs to be of the expected length and have the expected hrp - if (str.size() != bech32_mod::DOUBLE_PUBKEY_ENC_SIZE - || hrp != params.Bech32ModHRP() - || str[params.Bech32ModHRP().size()] != '1' - ) return std::nullopt; - - // decode to 5-bit based byte vector - const auto dec = bech32_mod::Decode(str); - - // check if it has expected encoding and the data is of the expected length - if ((dec.encoding != bech32_mod::Encoding::BECH32 && dec.encoding != bech32_mod::Encoding::BECH32M) - || dec.data.size() != 154 - ) return std::nullopt; - - // The data part consists of two concatenated 48-byte public keys - std::vector data; - data.reserve(blsct::DoublePublicKey::SIZE); - if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin(), dec.data.end())) { - return std::nullopt; - } - - blsct::DoublePublicKey dpk(data); - if (dpk.IsValid()) { - return dpk; - } else { - return std::nullopt; - } -} - } // namespace bech32_mod diff --git a/src/bech32_mod.h b/src/bech32_mod.h index a8a31672f44fc..625f04fedf7d5 100644 --- a/src/bech32_mod.h +++ b/src/bech32_mod.h @@ -15,7 +15,6 @@ #define BITCOIN_BECH32_MOD_H #include -#include #include #include #include @@ -24,13 +23,6 @@ namespace bech32_mod { -// double public key after encoding to bech32_mod is 165-byte long consisting of: -// - 2-byte hrp -// - 1-byte separator '1' -// - 154-byte key data (96 bytes / 5 bits = 153.6) -// - 8-byte checksum -constexpr size_t DOUBLE_PUBKEY_ENC_SIZE = 2 + 1 + 154 + 8; - enum class Encoding { INVALID, //!< Failed decoding @@ -55,18 +47,8 @@ struct DecodeResult /** Decode a Bech32 or Bech32m string. */ DecodeResult Decode(const std::string& str); -/** Encode DoublePublicKey to Bech32 or Bech32m string. Encoding must be one of BECH32 or BECH32M. */ -std::string EncodeDoublePublicKey( - const CChainParams& params, - const Encoding encoding, - const blsct::DoublePublicKey& dpk -); - -/** Decode a Bech32 or Bech32m string to a DoublePublicKey. */ -std::optional DecodeDoublePublicKey( - const CChainParams& params, - const std::string& str -); +// 96 bytes / 5 bits = 153.6 -> 154 bytes +constexpr size_t DOUBLE_PUBKEY_DATA_ENC_SIZE = 154; } // namespace bech32_mod diff --git a/src/key_io.cpp b/src/key_io.cpp index 39922035e55c3..7e89e78e82a20 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -29,7 +29,7 @@ class DestinationEncoder std::string operator()(const blsct::DoublePublicKey& id) const { - return bech32_mod::EncodeDoublePublicKey( + return EncodeDoublePublicKey( m_params, bech32_mod::Encoding::BECH32M, id @@ -91,7 +91,7 @@ class DestinationEncoder CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str, std::vector* error_locations) { // first try to decode str to a double public key - auto maybe_dpk = bech32_mod::DecodeDoublePublicKey(params, str); + auto maybe_dpk = DecodeDoublePublicKey(params, str); if (maybe_dpk) { auto dpk = maybe_dpk.value(); if (dpk.IsValid()) { @@ -320,3 +320,53 @@ bool IsValidDestinationString(const std::string& str) { return IsValidDestinationString(str, Params()); } + +std::string EncodeDoublePublicKey( + const CChainParams& params, + const bech32_mod::Encoding encoding, + const blsct::DoublePublicKey& dpk +) { + std::vector dpk_v8 = dpk.GetVch(); + std::vector dpk_v5; + dpk_v5.reserve(DOUBLE_PUBKEY_ENC_SIZE); + + // ignoring the return value since this conversion always succeeds + ConvertBits<8, 5, true>([&](uint8_t c) { dpk_v5.push_back(c); }, dpk_v8.begin(), dpk_v8.end()); + + return Encode(encoding, params.Bech32ModHRP(), dpk_v5); +} + +std::optional DecodeDoublePublicKey( + const CChainParams& params, + const std::string& str +) { + const auto hrp = ToLower(str.substr(0, params.Bech32ModHRP().size())); + + // str needs to be of the expected length and have the expected hrp + if (str.size() != DOUBLE_PUBKEY_ENC_SIZE + || hrp != params.Bech32ModHRP() + || str[params.Bech32ModHRP().size()] != '1' + ) return std::nullopt; + + // decode to 5-bit based byte vector + const auto dec = bech32_mod::Decode(str); + + // check if it has expected encoding and the data is of the expected length + if ((dec.encoding != bech32_mod::Encoding::BECH32 && dec.encoding != bech32_mod::Encoding::BECH32M) + || dec.data.size() != 154 + ) return std::nullopt; + + // The data part consists of two concatenated 48-byte public keys + std::vector data; + data.reserve(blsct::DoublePublicKey::SIZE); + if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin(), dec.data.end())) { + return std::nullopt; + } + + blsct::DoublePublicKey dpk(data); + if (dpk.IsValid()) { + return dpk; + } else { + return std::nullopt; + } +} diff --git a/src/key_io.h b/src/key_io.h index 22f941796daad..1ca2ea0cea804 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_KEY_IO_H #define BITCOIN_KEY_IO_H +#include #include #include #include @@ -28,4 +29,24 @@ CTxDestination DecodeDestination(const std::string& str, std::string& error_msg, bool IsValidDestinationString(const std::string& str); bool IsValidDestinationString(const std::string& str, const CChainParams& params); +// double public key after encoding to bech32_mod is 165-byte long consisting of: +// - 2-byte hrp +// - 1-byte separator '1' +// - 154-byte data +// - 8-byte checksum +constexpr size_t DOUBLE_PUBKEY_ENC_SIZE = 2 + 1 + bech32_mod::DOUBLE_PUBKEY_DATA_ENC_SIZE + 8; + +/** Encode DoublePublicKey to Bech32 or Bech32m string. Encoding must be one of BECH32 or BECH32M. */ +std::string EncodeDoublePublicKey( + const CChainParams& params, + const bech32_mod::Encoding encoding, + const blsct::DoublePublicKey& dpk +); + +/** Decode a Bech32 or Bech32m string to a DoublePublicKey. */ +std::optional DecodeDoublePublicKey( + const CChainParams& params, + const std::string& str +); + #endif // BITCOIN_KEY_IO_H diff --git a/src/test/bech32_mod_tests.cpp b/src/test/bech32_mod_tests.cpp index d7898f77e3af0..ff878070f7a6f 100644 --- a/src/test/bech32_mod_tests.cpp +++ b/src/test/bech32_mod_tests.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "blsct/double_public_key.h" #include #include #include @@ -53,20 +54,15 @@ void embed_errors(std::string& s, const size_t num_errors) { } } -std::string gen_random_str(const size_t size) { - static const char charset[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - const size_t max_index = (sizeof(charset) - 1); +std::string gen_random_byte_str(const size_t size) { std::string s; std::random_device rd; std::mt19937 gen(rd()); - std::uniform_int_distribution dist(0, max_index); + std::uniform_int_distribution dist(0, 255); for (size_t i = 0; i < size; ++i) { - s += charset[dist(gen)]; + s += dist(gen); } return s; } @@ -80,8 +76,8 @@ size_t test_error_detection( for (size_t i=0; i dpk_v8(dpk.begin(), dpk.end()); @@ -122,3 +118,4 @@ BOOST_AUTO_TEST_CASE(bech32_mod_test_detecting_errors) } BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index 2eefa6b404f74..ee6040780ee37 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "script/standard.h" #include #include @@ -146,17 +147,30 @@ BOOST_AUTO_TEST_CASE(key_io_invalid) } } -BOOST_AUTO_TEST_CASE(key_io_blsct) +BOOST_AUTO_TEST_CASE(key_io_double_public_key_endode_decode) { - // Generate two random public keys - blsct::PublicKey keyFromPointRandom{MclG1Point::Rand()}; - blsct::PublicKey keyFromPointRandom2{MclG1Point::Rand()}; + // randomly generated double public key + blsct::PublicKey pk1(MclG1Point::Rand()); + blsct::PublicKey pk2(MclG1Point::Rand()); + blsct::DoublePublicKey dpk(pk1, pk2); - blsct::DoublePublicKey doubleKey{keyFromPointRandom, keyFromPointRandom2}; + // check if encoding and then decoding it + // produces the original double public key + auto act = DecodeDestination(EncodeDestination(dpk.GetVch())); - auto dest = EncodeDestination(doubleKey); + BOOST_CHECK(act == CTxDestination(dpk)); +} + +BOOST_AUTO_TEST_CASE(key_io_double_public_key_decode_encode) +{ + // a valid bech32_mod encoded double public key + std::string dpk_bech32_mod = "nv1jlca8fe3jltegf54vwxyl2dvplpk3rz0ja6tjpdpfcar79cm43vxc40g8luh5xh0lva0qzkmytrthftje04fqnt8g6yq3j8t2z552ryhy8dnpyfgqyj58ypdptp43f32u28htwu0r37y9su6332jn0c0fcvan8l53m"; + + // check if decoding and then encoding it produces + // the original bech32_mod encoded double public key + auto act = EncodeDestination(DecodeDestination(dpk_bech32_mod)); - BOOST_CHECK(DecodeDestination(dest) == CTxDestination(doubleKey)); + BOOST_CHECK(act == dpk_bech32_mod); } BOOST_AUTO_TEST_SUITE_END() From 7139064b240f110a3f7fcd9283aa5d969f90c65e Mon Sep 17 00:00:00 2001 From: gogoex <110195520+gogoex@users.noreply.github.com> Date: Thu, 14 Dec 2023 08:05:22 +0900 Subject: [PATCH 6/8] fix headers --- src/bech32_mod.cpp | 2 +- src/test/bech32_mod_tests.cpp | 2 +- src/test/key_io_tests.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bech32_mod.cpp b/src/bech32_mod.cpp index f6921ee9f686a..8ba07d9422564 100644 --- a/src/bech32_mod.cpp +++ b/src/bech32_mod.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "blsct/double_public_key.h" +#include #include #include diff --git a/src/test/bech32_mod_tests.cpp b/src/test/bech32_mod_tests.cpp index ff878070f7a6f..d4367e247ffaa 100644 --- a/src/test/bech32_mod_tests.cpp +++ b/src/test/bech32_mod_tests.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "blsct/double_public_key.h" +#include #include #include #include diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index ee6040780ee37..2ccbbca409432 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "script/standard.h" +#include