-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathBase58.cs
217 lines (182 loc) · 7.88 KB
/
Base58.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/* Josip Medved <jmedved@jmedved.com> * www.medo64.com * MIT License */
//2023-01-11: Refactored to remove endian reversals
//2019-10-04: Refactored for .NET 5
//2019-03-09: Initial version
namespace Medo.Convert;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
/// <summary>
/// Base58 encoder/decoder with leading-zero preservation.
/// </summary>
/// <example>
/// <code>
/// var inString = "1LQX";
/// var outBytes = Base58.AsBytes(inString);
/// var outString = Base58.ToString(outBytes);
/// </code>
/// </example>
public static class Base58 {
private static readonly char[] Base58Map = new char[] {
'1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};
private static readonly BigInteger Base58Divisor = 58;
private static readonly Encoding Utf8 = new UTF8Encoding(false);
#region ToString
/// <summary>
/// Returns Base58 string.
/// </summary>
/// <param name="bytes">Bytes to convert.</param>
/// <exception cref="NullReferenceException">Bytes cannot be null.</exception>
public static string ToString(byte[] bytes) {
if (bytes == null) { throw new ArgumentNullException(nameof(bytes), "Bytes cannot be null."); }
if (TryToString(bytes, out var result)) { return result; }
throw new FormatException("Cannot convert.");
}
/// <summary>
/// Returns Base58 string.
/// </summary>
/// <param name="text">UTF-8 text to convert.</param>
/// <exception cref="NullReferenceException">Text cannot be null.</exception>
public static string ToString(string text) {
if (text == null) { throw new ArgumentNullException(nameof(text), "Text cannot be null."); }
return ToString(Utf8.GetBytes(text));
}
/// <summary>
/// Returns true of conversion succeeded.
/// </summary>
/// <param name="bytes">Bytes to convert.</param>
/// <param name="result">Converstion result as Base58 string.</param>
/// <exception cref="NullReferenceException">Bytes cannot be null.</exception>
public static bool TryToString(byte[] bytes, out string result) {
if (bytes == null) { throw new ArgumentNullException(nameof(bytes), "Bytes cannot be null."); }
if (bytes.Length == 0) { //don't bother if there is nothing in array
result = "";
return true;
}
var input = new BigInteger(bytes, isUnsigned: true, isBigEndian: true);
var remainders = new List<int>();
while (input > 0) {
input = BigInteger.DivRem(input, Base58Divisor, out var remainder);
remainders.Add((int)remainder);
}
//preserve leading zeros
foreach (var b in bytes) {
if (b == 0) { remainders.Add(0); } else { break; }
}
remainders.Reverse();
var sbOutput = new StringBuilder();
foreach (var remainder in remainders) {
sbOutput.Append(Base58Map[remainder]);
}
result = sbOutput.ToString();
return true;
}
/// <summary>
/// Returns true of conversion succeeded.
/// </summary>
/// <param name="text">Text to convert.</param>
/// <param name="result">Converstion result as Base58 string.</param>
/// <exception cref="NullReferenceException">Text cannot be null.</exception>
public static bool TryToString(string text, out string result) {
if (text == null) { throw new ArgumentNullException(nameof(text), "Text cannot be null."); }
return TryToString(Utf8.GetBytes(text), out result);
}
#endregion ToString
#region AsBytes
/// <summary>
/// Returns bytes based on their Base58 encoding.
/// </summary>
/// <param name="base58">Base58 encoded string.</param>
/// <exception cref="NullReferenceException">Base58 string cannot be null.</exception>
/// <exception cref="FormatException">Unknown character.</exception>
public static byte[] AsBytes(string base58) {
if (base58 == null) { throw new ArgumentNullException(nameof(base58), "Base58 string cannot be null."); }
if (TryAsBytes(base58, out var result)) { return result; }
throw new FormatException("Cannot convert.");
}
/// <summary>
/// Returns true of conversion succeeded.
/// </summary>
/// <param name="base58">Base58 encoded string.</param>
/// <param name="result">Conversion result as bytes.</param>
/// <exception cref="NullReferenceException">Base58 string cannot be null.</exception>
/// <exception cref="FormatException">Unknown character.</exception>
public static bool TryAsBytes(string base58, out byte[] result) {
if (base58 == null) { throw new ArgumentNullException(nameof(base58), "Base58 string cannot be null."); }
if (string.IsNullOrEmpty(base58)) {
result = Array.Empty<byte>();
return true;
}
var inStarting = true;
var startingZeros = 0;
var indices = new List<int>();
foreach (var c in base58) {
var index = Array.IndexOf(Base58Map, c);
if (index >= 0) {
if (inStarting && (index == 0)) {
startingZeros += 1;
} else {
inStarting = false;
}
indices.Add(index);
} else {
throw new FormatException("Unknown character.");
}
}
var output = new BigInteger();
foreach (var index in indices) {
output = BigInteger.Multiply(output, Base58Divisor);
output = BigInteger.Add(output, index);
}
var outputBytes = output.ToByteArray(isUnsigned: false, isBigEndian: true);
try {
var extraZeros = (outputBytes[0] == 0x00) ? startingZeros - 1 : startingZeros;
var buffer = new byte[outputBytes.Length + extraZeros];
if (extraZeros >= 0) {
Buffer.BlockCopy(outputBytes, 0, buffer, extraZeros, outputBytes.Length);
} else {
Buffer.BlockCopy(outputBytes, -extraZeros, buffer, 0, buffer.Length);
}
result = buffer;
return true;
} finally {
Array.Clear(outputBytes, 0, outputBytes.Length);
}
}
#endregion AsBytes
#region AsString
/// <summary>
/// Returns UTF-8 string based on it's Base58 encoding.
/// </summary>
/// <param name="base58">Base58 encoded string.</param>
/// <exception cref="NullReferenceException">Base58 string cannot be null.</exception>
/// <exception cref="FormatException">Unknown character.</exception>
public static string? AsString(string base58) {
if (base58 == null) { throw new ArgumentNullException(nameof(base58), "Base58 string cannot be null."); }
if (TryAsString(base58, out var result)) { return result; }
throw new FormatException("Cannot convert.");
}
/// <summary>
/// Returns true of conversion succeeded.
/// </summary>
/// <param name="base58">Base58 encoded string.</param>
/// <param name="result">Conversion result as UTF-8 string.</param>
/// <exception cref="NullReferenceException">Base58 string cannot be null.</exception>
/// <exception cref="FormatException">Unknown character.</exception>
public static bool TryAsString(string base58, out string? result) {
if (base58 == null) { throw new ArgumentNullException(nameof(base58), "Base58 string cannot be null."); }
if (TryAsBytes(base58, out var bytes)) {
result = Utf8.GetString(bytes);
return true;
} else {
result = null;
return false;
}
}
#endregion AsString
}