Skip to content

Commit e31a9a1

Browse files
committed
[Polkadot] Ss58 decoding\encoding support
Resolves brave/brave-browser#44748
1 parent 190b632 commit e31a9a1

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

components/brave_wallet/common/encoding_utils.cc

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,28 @@
55

66
#include "brave/components/brave_wallet/common/encoding_utils.h"
77

8+
#include <utility>
9+
10+
#include "base/containers/span.h"
11+
#include "base/containers/span_writer.h"
812
#include "brave/components/brave_wallet/common/hash_utils.h"
913
#include "brave/third_party/bitcoin-core/src/src/base58.h"
1014

1115
namespace brave_wallet {
1216

17+
namespace {
18+
// Prefix is added a public key to calculate
19+
// blake2b hash.
20+
constexpr char kSs58HashPrefix[] = "SS58PRE";
21+
constexpr size_t kSs58HashPrefixSize = 7u;
22+
constexpr size_t kSs58HashChecksumSize = 2u;
23+
} // namespace
24+
25+
Ss58Address::Ss58Address() = default;
26+
Ss58Address::~Ss58Address() = default;
27+
Ss58Address::Ss58Address(Ss58Address&& addr) = default;
28+
Ss58Address& Ss58Address::operator=(Ss58Address&& addr) = default;
29+
1330
std::string Base58EncodeWithCheck(const std::vector<uint8_t>& bytes) {
1431
auto with_checksum = bytes;
1532
auto checksum = DoubleSHA256Hash(bytes);
@@ -42,4 +59,100 @@ std::string Base58Encode(base::span<const uint8_t> bytes) {
4259
return EncodeBase58(bytes);
4360
}
4461

62+
// Reference implementation
63+
// https://github.com/gear-tech/gear/blob/7d481fed39e7b0633ca9afeed8ce1b3cbb636f3e/utils/ss58/src/lib.rs#L295
64+
std::optional<std::string> Ss58Encode(const Ss58Address& addr) {
65+
uint16_t prefix = addr.prefix;
66+
if (prefix > 16383) {
67+
return std::nullopt;
68+
}
69+
70+
std::vector<uint8_t> buff;
71+
size_t offset = prefix < 64 ? 1 : 2;
72+
buff.resize(offset + kSs58PublicKeySize + kSs58HashChecksumSize);
73+
auto output_span_writer = base::SpanWriter(base::span(buff));
74+
75+
if (offset == 1) {
76+
output_span_writer.WriteU8BigEndian(prefix);
77+
} else {
78+
output_span_writer.WriteU8BigEndian(((prefix & 0b11111100) >> 2) |
79+
0b01000000);
80+
output_span_writer.WriteU8BigEndian((prefix >> 8) | ((prefix & 0b11) << 6));
81+
}
82+
83+
output_span_writer.Write(base::span(addr.public_key));
84+
DCHECK_EQ(output_span_writer.remaining(), kSs58HashChecksumSize);
85+
DCHECK_EQ(buff.size(), offset + kSs58PublicKeySize + kSs58HashChecksumSize);
86+
87+
std::vector<uint8_t> hash_input;
88+
hash_input.resize(kSs58HashPrefixSize + offset + kSs58PublicKeySize);
89+
auto hash_input_writer = base::SpanWriter(base::span(hash_input));
90+
hash_input_writer.Write(base::byte_span_from_cstring(kSs58HashPrefix));
91+
hash_input_writer.Write(base::span(buff).first(offset + kSs58PublicKeySize));
92+
DCHECK_EQ(hash_input_writer.remaining(), 0u);
93+
auto hash = Blake2bHash<64>(base::span(hash_input));
94+
95+
output_span_writer.Write(base::span(hash).first(kSs58HashChecksumSize));
96+
97+
return Base58Encode(buff);
98+
}
99+
100+
// Reference implementation
101+
// https://github.com/gear-tech/gear/blob/7d481fed39e7b0633ca9afeed8ce1b3cbb636f3e/utils/ss58/src/lib.rs#L243
102+
std::optional<Ss58Address> Ss58Decode(const std::string& str) {
103+
auto result =
104+
Base58Decode(str, kSs58HashChecksumSize + kSs58PublicKeySize + 2, false);
105+
if (!result ||
106+
result->size() < kSs58HashChecksumSize + kSs58PublicKeySize + 1) {
107+
return std::nullopt;
108+
}
109+
uint8_t offset = 0;
110+
uint8_t address_type = (*result)[0];
111+
uint16_t prefix = 0;
112+
if (address_type < 64) {
113+
offset = 1;
114+
prefix = address_type;
115+
} else if (address_type < 128) {
116+
offset = 2;
117+
uint8_t address_type_1 = (*result)[1];
118+
uint8_t lower = ((address_type << 2) | (address_type_1 >> 6)) & 0b11111111;
119+
uint8_t upper = address_type_1 & 0b00111111;
120+
prefix = lower | (upper << 8);
121+
} else {
122+
return std::nullopt;
123+
}
124+
125+
if (result->size() != offset + kSs58PublicKeySize + kSs58HashChecksumSize) {
126+
return std::nullopt;
127+
}
128+
129+
// Prepare input to calculate checksum
130+
std::vector<uint8_t> hash_input(kSs58HashPrefixSize + offset +
131+
kSs58PublicKeySize);
132+
auto hash_input_writer = base::SpanWriter(base::span(hash_input));
133+
hash_input_writer.Write(base::byte_span_from_cstring(kSs58HashPrefix));
134+
hash_input_writer.Write(
135+
base::span(*result).first(offset + kSs58PublicKeySize));
136+
DCHECK_EQ(hash_input_writer.remaining(), 0u);
137+
auto hash = Blake2bHash<64>(base::span(hash_input));
138+
139+
// Verify checksum
140+
for (size_t i = 0; i < kSs58HashChecksumSize; i++) {
141+
// Checking last 2 bytes
142+
if (hash[i] != (*result)[offset + kSs58PublicKeySize + i]) {
143+
return std::nullopt;
144+
}
145+
}
146+
147+
// Prepare output
148+
Ss58Address result_addr;
149+
result_addr.prefix = prefix;
150+
auto public_key_span_writer =
151+
base::SpanWriter(base::span(result_addr.public_key));
152+
public_key_span_writer.Write(
153+
base::span(*result).subspan(offset, kSs58PublicKeySize));
154+
DCHECK_EQ(public_key_span_writer.remaining(), 0u);
155+
return std::move(result_addr);
156+
}
157+
45158
} // namespace brave_wallet

components/brave_wallet/common/encoding_utils.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@
1313

1414
namespace brave_wallet {
1515

16+
// Size of Ed25519\Sr25519 public keys.
17+
inline constexpr size_t kSs58PublicKeySize = 32u;
18+
19+
// Encodes Ed25519 pr Sr25519 public key adding
20+
// special prefix and checksum.
21+
struct Ss58Address {
22+
Ss58Address();
23+
~Ss58Address();
24+
Ss58Address(Ss58Address& addr) = delete;
25+
Ss58Address& operator=(const Ss58Address& addr) = delete;
26+
Ss58Address(Ss58Address&& addr);
27+
Ss58Address& operator=(Ss58Address&& addr);
28+
uint16_t prefix;
29+
// ed25519 or sr25519 public key.
30+
std::array<uint8_t, kSs58PublicKeySize> public_key;
31+
};
32+
1633
// A bridge function to call DecodeBase58 in bitcoin-core.
1734
// It will return false if length of decoded byte array does not match len
1835
// param.
@@ -27,6 +44,9 @@ std::optional<std::vector<uint8_t>> Base58Decode(const std::string& str,
2744
std::string Base58Encode(base::span<const uint8_t> bytes);
2845
std::string Base58EncodeWithCheck(const std::vector<uint8_t>& bytes);
2946

47+
std::optional<std::string> Ss58Encode(const Ss58Address& addr);
48+
std::optional<Ss58Address> Ss58Decode(const std::string& str);
49+
3050
} // namespace brave_wallet
3151

3252
#endif // BRAVE_COMPONENTS_BRAVE_WALLET_COMMON_ENCODING_UTILS_H_

components/brave_wallet/common/encoding_utils_unittest.cc

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,127 @@ TEST(EncodingUtilsUnitTest, Base58EncodeWithCheck) {
7878
.value()));
7979
}
8080

81+
// Test vectors calculated using https://ss58.org
82+
TEST(EncodingUtilsUnitTest, Ss58Encode) {
83+
// Single-byte prefix
84+
{
85+
Ss58Address addr;
86+
EXPECT_TRUE(base::HexStringToSpan(
87+
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
88+
base::span(addr.public_key)));
89+
addr.prefix = 0;
90+
EXPECT_EQ("15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5",
91+
Ss58Encode(addr).value());
92+
93+
auto decoded =
94+
Ss58Decode("15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5");
95+
EXPECT_EQ(decoded->public_key, addr.public_key);
96+
EXPECT_EQ(decoded->prefix, 0u);
97+
}
98+
99+
{
100+
Ss58Address addr;
101+
EXPECT_TRUE(base::HexStringToSpan(
102+
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
103+
base::span(addr.public_key)));
104+
addr.prefix = 42;
105+
EXPECT_EQ("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
106+
Ss58Encode(addr).value());
107+
108+
auto decoded =
109+
Ss58Decode("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
110+
EXPECT_EQ(decoded->public_key, addr.public_key);
111+
EXPECT_EQ(decoded->prefix, 42u);
112+
}
113+
114+
{
115+
Ss58Address addr;
116+
EXPECT_TRUE(base::HexStringToSpan(
117+
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
118+
base::span(addr.public_key)));
119+
addr.prefix = 63;
120+
EXPECT_EQ("7NPoMQbiA6trJKkjB35uk96MeJD4PGWkLQLH7k7hXEkZpiba",
121+
Ss58Encode(addr).value());
122+
123+
auto decoded =
124+
Ss58Decode("7NPoMQbiA6trJKkjB35uk96MeJD4PGWkLQLH7k7hXEkZpiba");
125+
EXPECT_EQ(decoded->public_key, addr.public_key);
126+
EXPECT_EQ(decoded->prefix, 63u);
127+
}
128+
129+
// Two-bytes prefix
130+
{
131+
Ss58Address addr;
132+
EXPECT_TRUE(base::HexStringToSpan(
133+
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
134+
base::span(addr.public_key)));
135+
addr.prefix = 64u;
136+
EXPECT_EQ("cEaNSpz4PxFcZ7nT1VEKrKewH67rfx6MfcM6yKojyyPz7qaqp",
137+
Ss58Encode(addr).value());
138+
139+
auto decoded =
140+
Ss58Decode("cEaNSpz4PxFcZ7nT1VEKrKewH67rfx6MfcM6yKojyyPz7qaqp");
141+
EXPECT_EQ(decoded->public_key, addr.public_key);
142+
EXPECT_EQ(decoded->prefix, 64u);
143+
}
144+
145+
{
146+
Ss58Address addr;
147+
EXPECT_TRUE(base::HexStringToSpan(
148+
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
149+
base::span(addr.public_key)));
150+
addr.prefix = 200;
151+
EXPECT_EQ("sD4TrgAhf8c8tYheiyKQb19jvTWY5fGx3TD1nK1Ue61WHRnT4",
152+
Ss58Encode(addr).value());
153+
154+
auto decoded =
155+
Ss58Decode("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
156+
EXPECT_EQ(decoded->public_key, addr.public_key);
157+
EXPECT_EQ(decoded->prefix, 42u);
158+
}
159+
160+
{
161+
Ss58Address addr;
162+
EXPECT_TRUE(base::HexStringToSpan(
163+
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
164+
base::span(addr.public_key)));
165+
addr.prefix = 16383;
166+
EXPECT_EQ("yNa8JpqfFB3q8A29rCwSgxvdU94ufJw2yKKxDgznS5m1PoFvn",
167+
Ss58Encode(addr).value());
168+
169+
auto decoded =
170+
Ss58Decode("yNa8JpqfFB3q8A29rCwSgxvdU94ufJw2yKKxDgznS5m1PoFvn");
171+
EXPECT_EQ(decoded->public_key, addr.public_key);
172+
EXPECT_EQ(decoded->prefix, 16383u);
173+
}
174+
175+
// Wrong prefix encode
176+
{
177+
Ss58Address addr;
178+
EXPECT_TRUE(base::HexStringToSpan(
179+
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
180+
base::span(addr.public_key)));
181+
addr.prefix = 16384u;
182+
EXPECT_FALSE(Ss58Encode(addr));
183+
}
184+
185+
// Wrong prefix decode
186+
{
187+
EXPECT_FALSE(
188+
Ss58Decode("HHrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"));
189+
}
190+
191+
// Wrong addr size
192+
{
193+
EXPECT_FALSE(
194+
Ss58Decode("0Na8JpqfFB3q8A29rCwSgxvdU94ufJw2yKKxDgznS5m1PoFvn"));
195+
}
196+
197+
// Wrong checksum
198+
{
199+
EXPECT_FALSE(
200+
Ss58Decode("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQa"));
201+
}
202+
}
203+
81204
} // namespace brave_wallet

0 commit comments

Comments
 (0)