Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Base58 perfomance patch #3186

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 54 additions & 14 deletions src/Neo/Cryptography/Base58.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;

namespace Neo.Cryptography
Expand All @@ -25,6 +26,21 @@ public static class Base58
/// Represents the alphabet of the base-58 encoder.
/// </summary>
public const string Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
private const char ZeroChar = '1';
private static readonly BigInteger s_alphabetLength = Alphabet.Length;

#pragma warning disable format
private static readonly sbyte[] s_decodeMap =
[
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6,
7, 8, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17,
18, 19, 20, 21, -1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1,
-1, -1, -1, -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45,
46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
];
#pragma warning restore format

/// <summary>
/// Converts the specified <see cref="string"/>, which encodes binary data as base-58 digits, to an equivalent byte array. The encoded <see cref="string"/> contains the checksum of the binary data.
Expand Down Expand Up @@ -71,21 +87,30 @@ public static byte[] Decode(string input)
{
// Decode Base58 string to BigInteger
var bi = BigInteger.Zero;
for (int i = 0; i < input.Length; i++)
sbyte digit;
for (var i = 0; i < input.Length; i++)
{
int digit = Alphabet.IndexOf(input[i]);
if (digit < 0)
if (input[i] >= 123)
throw new FormatException($"Invalid Base58 character '{input[i]}' at position {i}");
digit = s_decodeMap[input[i]];
if (digit == -1)
throw new FormatException($"Invalid Base58 character '{input[i]}' at position {i}");
bi = bi * Alphabet.Length + digit;
bi = bi * s_alphabetLength + digit;
}

// Encode BigInteger to byte[]
// Leading zero bytes get encoded as leading `1` characters
int leadingZeroCount = input.TakeWhile(c => c == Alphabet[0]).Count();
var leadingZeros = new byte[leadingZeroCount];
if (bi.IsZero) return leadingZeros;
var bytesWithoutLeadingZeros = bi.ToByteArray(isUnsigned: true, isBigEndian: true);
return [.. leadingZeros, .. bytesWithoutLeadingZeros];

var leadingZeroCount = LeadingBase58Zeros(input);
if (bi.IsZero)
{
return new byte[leadingZeroCount];
}

var result = new byte[leadingZeroCount + bi.GetByteCount(true)];

_ = bi.TryWriteBytes(result.AsSpan(leadingZeroCount), out _, true, true);
return result;
}

/// <summary>
Expand All @@ -99,20 +124,35 @@ public static string Encode(ReadOnlySpan<byte> input)
BigInteger value = new(input, isUnsigned: true, isBigEndian: true);

// Encode BigInteger to Base58 string
var sb = new StringBuilder();
var sb = new StringBuilder(input.Length * 138 / 100 + 5);
shargon marked this conversation as resolved.
Show resolved Hide resolved

while (value > 0)
{
value = BigInteger.DivRem(value, Alphabet.Length, out var remainder);
sb.Insert(0, Alphabet[(int)remainder]);
value = BigInteger.DivRem(value, s_alphabetLength, out var remainder);
sb.Append(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]);
sb.Append(ZeroChar);
}
return sb.ToString();

Span<char> copy = stackalloc char[sb.Length];
sb.CopyTo(0, copy, sb.Length);
copy.Reverse();
shargon marked this conversation as resolved.
Show resolved Hide resolved

return copy.ToString();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int LeadingBase58Zeros(string collection)
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
var i = 0;
var len = collection.Length;
for (; i < len && collection[i] == ZeroChar; i++) { }

return i;
}
}
}
Loading