diff --git a/neo.UnitTests/Cryptography/UT_Base58.cs b/neo.UnitTests/Cryptography/UT_Base58.cs index 5f2d750382..fbf29ba888 100644 --- a/neo.UnitTests/Cryptography/UT_Base58.cs +++ b/neo.UnitTests/Cryptography/UT_Base58.cs @@ -2,30 +2,49 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using System; +using System.Collections.Generic; namespace Neo.UnitTests.Cryptography { [TestClass] public class UT_Base58 { - byte[] decoded1 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - string encoded1 = "1kA3B2yGe2z4"; - byte[] decoded2 = { 0, 0, 0, 0, 0 }; - string encoded2 = "1111"; - [TestMethod] - public void TestEncode() + public void TestEncodeDecode() { - Base58.Encode(decoded1).Should().Be(encoded1); - } + var bitcoinTest = new Dictionary() + { + // Tests from https://github.com/bitcoin/bitcoin/blob/46fc4d1a24c88e797d6080336e3828e45e39c3fd/src/test/data/base58_encode_decode.json - [TestMethod] - public void TestDecode() - { - Base58.Decode(encoded1).Should().BeEquivalentTo(decoded1); - Base58.Decode(encoded2).Should().BeEquivalentTo(decoded2); - Action action = () => Base58.Decode(encoded1 + "l").Should().BeEquivalentTo(decoded1); - action.Should().Throw(); + {"", ""}, + {"61", "2g"}, + {"626262", "a3gV"}, + {"636363", "aPEr"}, + {"73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"}, + {"00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"}, + {"516b6fcd0f", "ABnLTmg"}, + {"bf4f89001e670274dd", "3SEo3LWLoPntC"}, + {"572e4794", "3EFU7m"}, + {"ecac89cad93923c02321", "EJDM8drfXA6uyA"}, + {"10c8511e", "Rt5zm"}, + {"00000000000000000000", "1111111111"}, + {"000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"}, + {"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"}, + + // Extra tests + + {"00", "1"}, + {"00010203040506070809", "1kA3B2yGe2z4"}, + }; + + foreach (var entry in bitcoinTest) + { + Base58.Encode(entry.Key.HexToBytes()).Should().Be(entry.Value); + Base58.Decode(entry.Value).Should().BeEquivalentTo(entry.Key.HexToBytes()); + + Action action = () => Base58.Decode(entry.Value + "l"); + action.Should().Throw(); + } } } } diff --git a/neo/Cryptography/Base58.cs b/neo/Cryptography/Base58.cs index b64f12216b..9ddbea3dc8 100644 --- a/neo/Cryptography/Base58.cs +++ b/neo/Cryptography/Base58.cs @@ -11,45 +11,44 @@ public static class Base58 public static byte[] Decode(string input) { - BigInteger bi = BigInteger.Zero; - for (int i = input.Length - 1; i >= 0; i--) + // Decode Base58 string to BigInteger + var bi = BigInteger.Zero; + for (int i = 0; i < input.Length; i++) { - int index = Alphabet.IndexOf(input[i]); - if (index == -1) - throw new FormatException(); - bi += index * BigInteger.Pow(58, input.Length - 1 - 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; } - byte[] bytes = bi.ToByteArray(); - Array.Reverse(bytes); - bool stripSignByte = bytes.Length > 1 && bytes[0] == 0 && bytes[1] >= 0x80; - int leadingZeros = 0; - for (int i = 0; i < input.Length && input[i] == Alphabet[0]; i++) - { - leadingZeros++; - } - byte[] tmp = new byte[bytes.Length - (stripSignByte ? 1 : 0) + leadingZeros]; - Array.Copy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros, tmp.Length - leadingZeros); - Array.Clear(bytes, 0, bytes.Length); - return tmp; + + // 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]; + var bytesWithoutLeadingZeros = bi.ToByteArray() + .Reverse()// to big endian + .SkipWhile(b => b == 0);//strip sign byte + return leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray(); } public static string Encode(byte[] input) { + // Decode byte[] to BigInteger BigInteger value = new BigInteger(new byte[1].Concat(input).Reverse().ToArray()); - StringBuilder sb = new StringBuilder(); - while (value >= 58) + + // Encode BigInteger to Base58 string + var sb = new StringBuilder(); + + while (value > 0) { - BigInteger mod = value % 58; - sb.Insert(0, Alphabet[(int)mod]); - value /= 58; + value = BigInteger.DivRem(value, Alphabet.Length, out var remainder); + sb.Insert(0, Alphabet[(int)remainder]); } - sb.Insert(0, Alphabet[(int)value]); - foreach (byte b in input) + + // Append `1` for each leading 0 byte + for (int i = 0; i < input.Length && input[i] == 0; i++) { - if (b == 0) - sb.Insert(0, Alphabet[0]); - else - break; + sb.Insert(0, Alphabet[0]); } return sb.ToString(); }