Skip to content

Commit e364f58

Browse files
committed
[Polkadot] Ss58 decoding\encoding support
Resolves brave/brave-browser#44748
1 parent 42f4e80 commit e364f58

File tree

3 files changed

+185
-143
lines changed

3 files changed

+185
-143
lines changed

components/brave_wallet/common/encoding_utils.cc

Lines changed: 57 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@
66
#include "brave/components/brave_wallet/common/encoding_utils.h"
77

88
#include "base/containers/span.h"
9+
#include "base/containers/span_writer.h"
910
#include "base/containers/to_vector.h"
1011
#include "brave/components/brave_wallet/common/hash_utils.h"
1112
#include "brave/third_party/bitcoin-core/src/src/base58.h"
1213

1314
namespace brave_wallet {
1415

1516
namespace {
16-
constexpr char kSs58HashPersonalizer[] = "SS58PRE";
17-
}
17+
constexpr char kSs58HashPrefix[] = "SS58PRE";
18+
constexpr size_t kSs58HashPrefixSize = 7u;
19+
constexpr size_t kSs58HashChecksumSize = 2u;
20+
} // namespace
1821

1922
Ss58Address::Ss58Address() = default;
2023
Ss58Address::~Ss58Address() = default;
24+
Ss58Address::Ss58Address(Ss58Address&& addr) = default;
25+
Ss58Address& Ss58Address::operator=(Ss58Address&& addr) = default;
2126

2227
std::string Base58EncodeWithCheck(const std::vector<uint8_t>& bytes) {
2328
auto with_checksum = bytes;
@@ -51,185 +56,95 @@ std::string Base58Encode(base::span<const uint8_t> bytes) {
5156
return EncodeBase58(bytes);
5257
}
5358

54-
// let prefix = address.prefix
55-
// assert(Number.isInteger(prefix) && prefix >= 0 && prefix < 16384, 'invalid prefix')
56-
// let len = address.bytes.length
57-
// let hashLen: number
58-
// switch(len) {
59-
// case 1:
60-
// case 2:
61-
// case 4:
62-
// case 8:
63-
// hashLen = 1
64-
// break
65-
// case 32:
66-
// case 33:
67-
// hashLen = 2
68-
// break
69-
// default:
70-
// assert(false, 'invalid address length')
71-
// }
72-
// let buf
73-
// let offset
74-
// if (prefix < 64) {
75-
// buf = Buffer.allocUnsafe(1 + hashLen + len)
76-
// buf[0] = prefix
77-
// offset = 1
78-
// } else {
79-
// buf = Buffer.allocUnsafe(2 + hashLen + len)
80-
// buf[0] = ((prefix & 0b1111_1100) >> 2) | 0b01000000
81-
// buf[1] = (prefix >> 8) | ((prefix & 0b11) << 6)
82-
// offset = 2
83-
// }
84-
// buf.set(address.bytes, offset)
85-
// computeHash(buf, hashLen)
86-
// for (let i = 0; i < hashLen; i++) {
87-
// buf[offset + len + i] = HASH_BUF[i]
88-
// }
89-
// return base58.encode(buf)
90-
59+
// Reference implementation
60+
// https://github.com/gear-tech/gear/blob/7d481fed39e7b0633ca9afeed8ce1b3cbb636f3e/utils/ss58/src/lib.rs#L295
9161
std::optional<std::string> Ss58Encode(const Ss58Address& addr) {
9262
uint16_t prefix = addr.prefix;
93-
if (prefix > 16384) {
94-
return std::nullopt;
95-
}
96-
size_t hash_len = 0;
97-
size_t len = addr.public_key.size();
98-
if (len <= 8) {
99-
hash_len = 1;
100-
} else if (len <= 33) {
101-
hash_len = 2;
102-
} else {
63+
if (prefix > 16383) {
10364
return std::nullopt;
10465
}
10566

106-
size_t offset = 0;
10767
std::vector<uint8_t> buff;
108-
if (prefix < 64) {
109-
buff.reserve(1 + hash_len + len);
110-
buff.push_back(prefix);
111-
offset = 1;
68+
size_t offset = prefix < 64 ? 1 : 2;
69+
buff.resize(offset + kSs58PublicKeySize + kSs58HashChecksumSize);
70+
auto output_span_writer = base::SpanWriter(base::span(buff));
71+
72+
if (offset == 1) {
73+
output_span_writer.WriteU8BigEndian(prefix);
11274
} else {
113-
buff.reserve(2 + hash_len + len);
114-
buff.push_back(((prefix & 0b11111100) >> 2) | 0b01000000);
115-
buff.push_back((prefix >> 8) | ((prefix & 0b11) << 6));
116-
offset = 2;
75+
output_span_writer.WriteU8BigEndian(((prefix & 0b11111100) >> 2) | 0b01000000);
76+
output_span_writer.WriteU8BigEndian((prefix >> 8) | ((prefix & 0b11) << 6));
11777
}
11878

119-
std::ranges::insert(buff, addr.public_key);
120-
std::array<uint8_t, 64> hash;
121-
Blake2bHash(
122-
buff,
123-
hash,
124-
base::byte_span_from_cstring(kSs58HashPersonalizer));
79+
output_span_writer.Write(base::span(addr.public_key));
80+
DCHECK_EQ(output_span_writer.remaining(), kSs58HashChecksumSize);
81+
DCHECK_EQ(buff.size(), offset + kSs58PublicKeySize + kSs58HashChecksumSize);
12582

126-
// if (!hash) {
127-
// return std::nullopt;
128-
// }
83+
std::vector<uint8_t> hash_input(kSs58HashPrefixSize + buff.size() - kSs58HashChecksumSize);
84+
auto hash_input_writer = base::SpanWriter(base::span(hash_input));
85+
hash_input_writer.Write(base::byte_span_from_cstring(kSs58HashPrefix));
86+
hash_input_writer.Write(base::span(buff).subspan(0u, buff.size() - kSs58HashChecksumSize));
87+
DCHECK_EQ(hash_input_writer.remaining(), 0u);
88+
auto hash = Blake2bHash<64>(base::span(hash_input));
12989

130-
for (size_t i = 0; i < hash_len; i++) {
131-
buff.push_back(hash[i]);
132-
}
90+
output_span_writer.Write(base::span(hash).subspan(0u, kSs58HashChecksumSize));
13391

13492
return Base58Encode(buff);
13593
}
13694

13795

138-
// export function decode(s: string): Address {
139-
// let buf = base58.decodeUnsafe(s)
140-
// if (buf == null || buf.length < 3) throw invalidAddress(s)
141-
// let b0 = buf[0]
142-
// let offset
143-
// let prefix
144-
// if (b0 < 64) {
145-
// prefix = b0
146-
// offset = 1
147-
// } else if (b0 < 128) {
148-
// let b1 = buf[1]
149-
// let lower = ((b0 << 2) | (b1 >> 6)) & 0b11111111
150-
// let upper = b1 & 0b00111111
151-
// prefix = lower | (upper << 8)
152-
// offset = 2
153-
// } else {
154-
// throw invalidAddress(s)
155-
// }
156-
// let hashLen: number
157-
// switch(buf.length - offset) {
158-
// case 34:
159-
// case 35:
160-
// hashLen = 2
161-
// break
162-
// case 9:
163-
// case 5:
164-
// case 3:
165-
// case 2:
166-
// hashLen = 1
167-
// break
168-
// default:
169-
// throw invalidAddress(s)
170-
// }
171-
// computeHash(buf, hashLen)
172-
// for (let i = 0; i < hashLen; i++) {
173-
// if (HASH_BUF[i] != buf[buf.length - hashLen + i]) {
174-
// throw invalidAddress(s)
175-
// }
176-
// }
177-
// return {
178-
// prefix,
179-
// bytes: buf.subarray(offset, buf.length - hashLen)
180-
// }
181-
// }
96+
// Reference implementation
97+
// https://github.com/gear-tech/gear/blob/7d481fed39e7b0633ca9afeed8ce1b3cbb636f3e/utils/ss58/src/lib.rs#L243
18298
std::optional<Ss58Address> Ss58Decode(const std::string& str) {
183-
auto result = Base58Decode(str, &result, 0, false);
184-
if (!result || result->length < 3) {
99+
auto result = Base58Decode(str, 36, false);
100+
if (!result || result->size() < kSs58HashChecksumSize + kSs58PublicKeySize + 1) {
185101
return std::nullopt;
186102
}
187103
uint8_t offset = 0;
188-
uint8_t address_type = result[0];
104+
uint8_t address_type = (*result)[0];
189105
uint16_t prefix = 0;
190106
if (address_type < 64) {
191107
offset = 1;
192108
prefix = address_type;
193109
} else if (address_type < 128) {
194110
offset = 2;
195-
uint8_t address_type_1 = result[1];
111+
uint8_t address_type_1 = (*result)[1];
196112
uint8_t lower = ((address_type << 2) | (address_type_1 >> 6)) & 0b11111111;
197113
uint8_t upper = address_type_1 & 0b00111111;
198114
prefix = lower | (upper << 8);
199115
} else {
200116
return std::nullopt;
201117
}
202118

203-
size_t hash_len = 0;
204-
switch (result->size() - offset) {
205-
case 34:
206-
case 35:
207-
hash_len = 2;
208-
break;
209-
case 9:
210-
case 5:
211-
case 3:
212-
case 2:
213-
hash_len = 1;
214-
break;
215-
default:
216-
return std::nullopt;
119+
if (result->size() != offset + kSs58HashChecksumSize + kSs58PublicKeySize) {
120+
return std::nullopt;
217121
}
218122

219-
auto hash = Blake2bHash<64>(
220-
base::span(*result).subspan(result->length() - hash_len),
221-
base::byte_span_from_cstring(kSs58HashPersonalizer));
222-
223-
for (size_t i = 0; i < hash_len; i++) {
224-
if (hash[i] != *result[result->length() - hash_len + i]) {
123+
// Prepare input to calculate checksum
124+
std::vector<uint8_t> hash_input(kSs58HashPrefixSize + result->size() -
125+
kSs58HashChecksumSize);
126+
auto hash_input_writer = base::SpanWriter(base::span(hash_input));
127+
hash_input_writer.Write(base::byte_span_from_cstring(kSs58HashPrefix));
128+
hash_input_writer.Write(
129+
base::span(*result).subspan(0u, result->size() - kSs58HashChecksumSize));
130+
DCHECK_EQ(hash_input_writer.remaining(), 0u);
131+
auto hash = Blake2bHash<64>(base::span(hash_input));
132+
133+
// Verify checksum
134+
for (size_t i = 0; i < kSs58HashChecksumSize; i++) {
135+
if (hash[i] != (*result)[result->size() - kSs58HashChecksumSize + i]) {
225136
return std::nullopt;
226137
}
227138
}
228139

229-
return Ss58Address{
230-
prefix,
231-
base::ToVector(base::span(*result)).subspan(offset, result->size() - hash_len)
232-
};
140+
// Prepare output
141+
Ss58Address result_addr;
142+
result_addr.prefix = prefix;
143+
auto public_key_span_writer = base::SpanWriter(base::span(result_addr.public_key));
144+
public_key_span_writer.Write(base::span(*result).subspan(
145+
offset, result->size() - kSs58HashChecksumSize - offset));
146+
DCHECK_EQ(public_key_span_writer.remaining(), 0u);
147+
return std::move(result_addr);
233148
}
234149

235150
} // namespace brave_wallet

components/brave_wallet/common/encoding_utils.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@
1313

1414
namespace brave_wallet {
1515

16+
constexpr size_t kSs58PublicKeySize = 32;
17+
1618
struct Ss58Address {
1719
Ss58Address();
1820
~Ss58Address();
21+
Ss58Address(Ss58Address& addr) = delete;
22+
Ss58Address& operator=(const Ss58Address& addr) = delete;
23+
Ss58Address(Ss58Address&& addr);
24+
Ss58Address& operator=(Ss58Address&& addr);
1925
uint16_t prefix;
20-
std::vector<uint8_t> public_key;
26+
// ed25519 or sr25519 public key.
27+
std::array<uint8_t, kSs58PublicKeySize> public_key;
2128
};
2229

2330
// A bridge function to call DecodeBase58 in bitcoin-core.

0 commit comments

Comments
 (0)