Skip to content

Commit

Permalink
Support Bech32M, Segwit v1, BIP350 (trustwallet#1345)
Browse files Browse the repository at this point in the history
* Support Bech32M, Segwit v1, BIP350

* Review findings

Co-authored-by: Catenocrypt <catenocrypt@users.noreply.github.com>
  • Loading branch information
2 people authored and cornbread78 committed Dec 22, 2021
1 parent f750553 commit e15faad
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 119 deletions.
66 changes: 47 additions & 19 deletions src/Bech32.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright © 2017 Pieter Wuille
// Copyright © 2017-2020 Trust Wallet.
// Copyright © 2017-2021 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
Expand All @@ -10,6 +10,11 @@

#include <array>

// Bech32 address encoding
// Bech32M variant also supported (BIP350)
// Max length of 90 constraint is extended here to 120 for other usages

using namespace TW::Bech32;
using namespace TW;

namespace {
Expand All @@ -26,11 +31,9 @@ constexpr std::array<int8_t, 128> charset_rev = {
6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27,
19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1};

/** Concatenate two byte arrays. */
Data cat(Data x, const Data& y) {
x.insert(x.end(), y.begin(), y.end());
return x;
}
const uint32_t BECH32_XOR_CONST = 0x01;
const uint32_t BECH32M_XOR_CONST = 0x2bc830a3;


/** Find the polynomial with value coefficients mod the generator as 30-bit. */
uint32_t polymod(const Data& values) {
Expand Down Expand Up @@ -62,16 +65,35 @@ Data expand_hrp(const std::string& hrp) {
return ret;
}

inline uint32_t xorConstant(ChecksumVariant variant) {
if (variant == ChecksumVariant::Bech32) {
return BECH32_XOR_CONST;
}
// Bech32M
return BECH32M_XOR_CONST;
}

/** Verify a checksum. */
bool verify_checksum(const std::string& hrp, const Data& values) {
return polymod(cat(expand_hrp(hrp), values)) == 1;
ChecksumVariant verify_checksum(const std::string& hrp, const Data& values) {
Data enc = expand_hrp(hrp);
append(enc, values);
auto poly = polymod(enc);
if (poly == BECH32_XOR_CONST) {
return ChecksumVariant::Bech32;
}
if (poly == BECH32M_XOR_CONST) {
return ChecksumVariant::Bech32M;
}
return ChecksumVariant::None;
}

/** Create a checksum. */
Data create_checksum(const std::string& hrp, const Data& values) {
Data enc = cat(expand_hrp(hrp), values);
Data create_checksum(const std::string& hrp, const Data& values, ChecksumVariant variant) {
Data enc = expand_hrp(hrp);
append(enc, values);
enc.resize(enc.size() + 6);
uint32_t mod = polymod(enc) ^ 1;
auto xorConst = xorConstant(variant);
uint32_t mod = polymod(enc) ^ xorConst;
Data ret;
ret.resize(6);
for (size_t i = 0; i < 6; ++i) {
Expand All @@ -83,9 +105,10 @@ Data create_checksum(const std::string& hrp, const Data& values) {
} // namespace

/** Encode a Bech32 string. */
std::string Bech32::encode(const std::string& hrp, const Data& values) {
Data checksum = create_checksum(hrp, values);
Data combined = cat(values, checksum);
std::string Bech32::encode(const std::string& hrp, const Data& values, ChecksumVariant variant) {
Data checksum = create_checksum(hrp, values, variant);
Data combined = values;
append(combined, checksum);
std::string ret = hrp + '1';
ret.reserve(ret.size() + combined.size());
for (const auto& value : combined) {
Expand All @@ -95,7 +118,11 @@ std::string Bech32::encode(const std::string& hrp, const Data& values) {
}

/** Decode a Bech32 string. */
std::pair<std::string, Data> Bech32::decode(const std::string& str) {
std::tuple<std::string, Data, ChecksumVariant> Bech32::decode(const std::string& str) {
if (str.length() > 120 || str.length() < 2) {
// too long or too short
return std::make_tuple(std::string(), Data(), None);
}
bool lower = false, upper = false;
bool ok = true;
for (size_t i = 0; ok && i < str.size(); ++i) {
Expand All @@ -114,7 +141,7 @@ std::pair<std::string, Data> Bech32::decode(const std::string& str) {
ok = false;
}
size_t pos = str.rfind('1');
if (ok && str.size() <= 120 && pos != str.npos && pos >= 1 && pos + 7 <= str.size()) {
if (ok && pos != str.npos && pos >= 1 && pos + 7 <= str.size()) {
Data values;
values.resize(str.size() - 1 - pos);
for (size_t i = 0; i < str.size() - 1 - pos; ++i) {
Expand All @@ -129,10 +156,11 @@ std::pair<std::string, Data> Bech32::decode(const std::string& str) {
for (size_t i = 0; i < pos; ++i) {
hrp += lc(str[i]);
}
if (verify_checksum(hrp, values)) {
return std::make_pair(hrp, Data(values.begin(), values.end() - 6));
auto variant = verify_checksum(hrp, values);
if (variant != None) {
return std::make_tuple(hrp, Data(values.begin(), values.end() - 6), variant);
}
}
}
return std::make_pair(std::string(), Data());
return std::make_tuple(std::string(), Data(), None);
}
24 changes: 18 additions & 6 deletions src/Bech32.h
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
// Copyright © 2017 Pieter Wuille
// Copyright © 2017-2020 Trust Wallet.
// Copyright © 2017-2021 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Data.h"

#include <cstdint>
#include <string>
#include <vector>
#include <tuple>

namespace TW::Bech32 {

enum ChecksumVariant {
None = 0,
Bech32,
Bech32M,
};

/// Encodes a Bech32 string.
///
/// \returns the encoded string, or an empty string in case of failure.
std::string encode(const std::string& hrp, const std::vector<uint8_t>& values);
std::string encode(const std::string& hrp, const Data& values, ChecksumVariant variant);

/// Decodes a Bech32 string.
///
/// \returns a pair with the human-readable part and the data, or a pair or
/// empty collections on failure.
std::pair<std::string, std::vector<uint8_t>> decode(const std::string& str);
/// \returns a tuple with
/// - the human-readable part
/// - data, or a pair or
/// - ChecksumVariant used
/// or empty values on failure.
std::tuple<std::string, Data, ChecksumVariant> decode(const std::string& str);

/// Converts from one power-of-2 number base to another.
template <int frombits, int tobits, bool pad>
inline bool convertBits(std::vector<uint8_t>& out, const std::vector<uint8_t>& in) {
inline bool convertBits(Data& out, const Data& in) {
int acc = 0;
int bits = 0;
const int maxv = (1 << tobits) - 1;
Expand Down
18 changes: 9 additions & 9 deletions src/Bech32Address.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2017-2020 Trust Wallet.
// Copyright © 2017-2021 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
Expand All @@ -18,15 +18,15 @@ bool Bech32Address::isValid(const std::string& addr) {
bool Bech32Address::isValid(const std::string& addr, const std::string& hrp) {
auto dec = Bech32::decode(addr);
// check hrp prefix (if given)
if (hrp.length() > 0 && dec.first.compare(0, hrp.length(), hrp) != 0) {
if (hrp.length() > 0 && std::get<0>(dec).compare(0, hrp.length(), hrp) != 0) {
return false;
}
if (dec.second.empty()) {
if (std::get<1>(dec).empty()) {
return false;
}

Data conv;
auto success = Bech32::convertBits<5, 8, false>(conv, dec.second);
auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(dec));
if (!success || conv.size() < 2 || conv.size() > 40) {
return false;
}
Expand All @@ -37,20 +37,20 @@ bool Bech32Address::isValid(const std::string& addr, const std::string& hrp) {
bool Bech32Address::decode(const std::string& addr, Bech32Address& obj_out, const std::string& hrp) {
auto dec = Bech32::decode(addr);
// check hrp prefix (if given)
if (hrp.length() > 0 && dec.first.compare(0, hrp.length(), hrp) != 0) {
if (hrp.length() > 0 && std::get<0>(dec).compare(0, hrp.length(), hrp) != 0) {
return false;
}
if (dec.second.empty()) {
if (std::get<1>(dec).empty()) {
return false;
}

Data conv;
auto success = Bech32::convertBits<5, 8, false>(conv, dec.second);
auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(dec));
if (!success || conv.size() < 2 || conv.size() > 40) {
return false;
}

obj_out.setHrp(dec.first);
obj_out.setHrp(std::get<0>(dec));
obj_out.setKey(conv);
return true;
}
Expand Down Expand Up @@ -94,7 +94,7 @@ std::string Bech32Address::string() const {
if (!Bech32::convertBits<8, 5, true>(enc, keyHash)) {
return "";
}
std::string result = Bech32::encode(hrp, enc);
std::string result = Bech32::encode(hrp, enc, Bech32::ChecksumVariant::Bech32);
// check back
Bech32Address obj;
if (!decode(result, obj, hrp)) {
Expand Down
38 changes: 27 additions & 11 deletions src/Bitcoin/SegwitAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ bool SegwitAddress::isValid(const std::string& string, const std::string& hrp) {
}
// extra step to check hrp
auto dec = Bech32::decode(string);
if (dec.first != hrp) {
if (std::get<0>(dec) != hrp) {
return false;
}

Expand All @@ -44,35 +44,51 @@ SegwitAddress::SegwitAddress(const PublicKey& publicKey, int witver, std::string
std::tuple<SegwitAddress, std::string, bool> SegwitAddress::decode(const std::string& addr) {
auto resp = std::make_tuple(SegwitAddress(), "", false);
auto dec = Bech32::decode(addr);
if (dec.second.empty()) {
auto& hrp = std::get<0>(dec);
auto& data = std::get<1>(dec);
auto& variant = std::get<2>(dec);
if (data.empty()) {
// bech32 decode fails, or decoded data is empty
return resp;
}
assert(dec.second.size() >= 1);
assert(data.size() >= 1);

// First byte is Segwit version
// Only version 0 is currently supported; BIP173 BIP350
if (dec.second[0] != 0) {
return resp;
auto segwitVersion = data[0];
if (segwitVersion == 0) {
// v0 uses Bech32 (not M)
if (variant != Bech32::ChecksumVariant::Bech32) {
return resp;
}
} else { // segwitVersion >= 1
// v1 uses Bech32M, BIP350
if (variant != Bech32::ChecksumVariant::Bech32M) {
return resp;
}
}

auto raw = fromRaw(dec.first, dec.second);
return std::make_tuple(raw.first, dec.first, raw.second);
auto raw = fromRaw(hrp, data);
return std::make_tuple(raw.first, hrp, raw.second);
}

std::string SegwitAddress::string() const {
Data enc;
enc.push_back(static_cast<uint8_t>(witnessVersion));
Bech32::convertBits<8, 5, true>(enc, witnessProgram);
std::string result = Bech32::encode(hrp, enc);
Bech32::ChecksumVariant variant = Bech32::ChecksumVariant::Bech32;
if (witnessVersion== 0) {
variant = Bech32::ChecksumVariant::Bech32;
} else if (witnessVersion >= 1) {
variant = Bech32::ChecksumVariant::Bech32M;
}
std::string result = Bech32::encode(hrp, enc, variant);
if (!std::get<2>(decode(result))) {
return {};
}
return result;
}

std::pair<SegwitAddress, bool> SegwitAddress::fromRaw(const std::string& hrp,
const std::vector<uint8_t>& data) {
std::pair<SegwitAddress, bool> SegwitAddress::fromRaw(const std::string& hrp, const Data& data) {
auto resp = std::make_pair(SegwitAddress(), false);
if (data.size() == 0) {
return resp;
Expand Down
10 changes: 5 additions & 5 deletions src/Bitcoin/SegwitAddress.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2017-2020 Trust Wallet.
// Copyright © 2017-2021 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
Expand All @@ -7,6 +7,7 @@
#pragma once

#include "../PublicKey.h"
#include "../Data.h"

#include <cstdint>
#include <string>
Expand All @@ -28,7 +29,7 @@ class SegwitAddress {
int witnessVersion;

/// Witness program.
std::vector<uint8_t> witnessProgram;
Data witnessProgram;

/// Determines whether a string makes a valid Bech32 address.
static bool isValid(const std::string& string);
Expand All @@ -39,7 +40,7 @@ class SegwitAddress {

/// Initializes a Bech32 address with a human-readable part, a witness
/// version, and a witness program.
SegwitAddress(std::string hrp, int witver, std::vector<uint8_t> witprog)
SegwitAddress(std::string hrp, int witver, Data witprog)
: hrp(std::move(hrp)), witnessVersion(witver), witnessProgram(std::move(witprog)) {}

/// Initializes a Bech32 address with a public key and a HRP prefix.
Expand All @@ -56,8 +57,7 @@ class SegwitAddress {
std::string string() const;

/// Initializes a Bech32 address with raw data.
static std::pair<SegwitAddress, bool> fromRaw(const std::string& hrp,
const std::vector<uint8_t>& data);
static std::pair<SegwitAddress, bool> fromRaw(const std::string& hrp, const Data& data);

bool operator==(const SegwitAddress& rhs) const {
return hrp == rhs.hrp && witnessVersion == rhs.witnessVersion &&
Expand Down
6 changes: 3 additions & 3 deletions src/Cardano/AddressV3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ using namespace std;
bool AddressV3::parseAndCheckV3(const std::string& addr, Discrimination& discrimination, Kind& kind, Data& key1, Data& key2) {
try {
auto bech = Bech32::decode(addr);
if (bech.second.size() == 0) {
if (std::get<1>(bech).size() == 0) {
// empty Bech data
return false;
}
// Bech bits conversion
Data conv;
auto success = Bech32::convertBits<5, 8, false>(conv, bech.second);
auto success = Bech32::convertBits<5, 8, false>(conv, std::get<1>(bech));
if (!success) {
return false;
}
Expand Down Expand Up @@ -213,7 +213,7 @@ string AddressV3::string(const std::string& hrp) const {
if (!Bech32::convertBits<8, 5, true>(bech, keys)) {
return "";
}
return Bech32::encode(hrp, bech);
return Bech32::encode(hrp, bech, Bech32::ChecksumVariant::Bech32);
}

string AddressV3::stringBase32() const {
Expand Down
Loading

0 comments on commit e15faad

Please sign in to comment.