Skip to content

Commit

Permalink
Fix Base58 issues (3x) (neo-project#1224)
Browse files Browse the repository at this point in the history
* Fix base58

* Optimize

* Update Base58.cs

* Revert some lines

* Rename

* Update Base58.cs
  • Loading branch information
shargon authored and Luchuan committed Jan 10, 2020
1 parent 693302c commit 482971d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 44 deletions.
49 changes: 34 additions & 15 deletions neo.UnitTests/Cryptography/UT_Base58.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>()
{
// 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<FormatException>();
{"", ""},
{"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<FormatException>();
}
}
}
}
57 changes: 28 additions & 29 deletions neo/Cryptography/Base58.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down

0 comments on commit 482971d

Please sign in to comment.