using System; using System.Linq; using System.Numerics; using System.Text; namespace FrostFS.SDK.Cryptography; public static class Base58 { public const string Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; public static byte[] Base58CheckDecode(this string input) { if (input is null) throw new ArgumentNullException(nameof(input)); byte[] buffer = Decode(input); if (buffer.Length < 4) throw new FormatException(); var check = buffer.AsSpan(0, buffer.Length - 4); byte[] checksum = DataHasher.Sha256(DataHasher.Sha256(check).AsSpan()); if (!buffer.AsSpan(buffer.Length - 4).SequenceEqual(checksum.AsSpan(0, 4))) throw new FormatException(); var result = check.ToArray(); Array.Clear(buffer, 0, buffer.Length); return result; } public static string Base58CheckEncode(this Span data) { byte[] checksum = DataHasher.Sha256(DataHasher.Sha256(data).AsSpan()); ; Span buffer = stackalloc byte[data.Length + 4]; data.CopyTo(buffer); checksum.AsSpan(0, 4).CopyTo(buffer.Slice(data.Length)); var ret = Encode(buffer); buffer.Clear(); return ret; } public static byte[] Decode(string input) { if (input == null) throw new ArgumentNullException(nameof(input)); // Decode Base58 string to BigInteger var bi = BigInteger.Zero; for (int i = 0; i < input.Length; i++) { int digit = Alphabet.IndexOf(input[i]); if (digit < 0) throw new FormatException($"Invalid Base58 character '{input[i]}' at position {i}"); bi = bi * Alphabet.Length + digit; } int leadingZeroCount = input.TakeWhile(c => c == Alphabet[0]).Count(); if (bi.IsZero) return new byte[leadingZeroCount]; var bytesBigEndian = bi.ToByteArray().Reverse().ToArray(); var firstNonZeroIndex = 0; while (bytesBigEndian.ElementAt(firstNonZeroIndex) == 0x0) firstNonZeroIndex++; var bytesWithoutLeadingZeros = bytesBigEndian.Skip(firstNonZeroIndex).ToArray(); var result = new byte[leadingZeroCount + bytesBigEndian.Length - firstNonZeroIndex]; int p = 0; while (p < leadingZeroCount) result[p++] = 0; for (int j = firstNonZeroIndex; j < bytesBigEndian.Length; j++) result[p++] = bytesBigEndian[j]; return result; } public static string Encode(ReadOnlySpan input) { var data = new byte[input.Length + 1]; ArrayHelper.GetRevertedArray(input, data); data[input.Length] = 0; BigInteger value = new(data); // Encode BigInteger to Base58 string var sb = new StringBuilder(); while (value > 0) { value = BigInteger.DivRem(value, Alphabet.Length, out var remainder); sb.Insert(0, Alphabet[(int)remainder]); } // Append `1` for each leading 0 byte for (int i = 0; i < input.Length && input[i] == 0; i++) { sb.Insert(0, Alphabet[0]); } return sb.ToString(); } }