在涉及到不同的项目不同语言,经常碰到编码问题,典型的就是base58和base64的编码问题
Base58是用于Bitcoin中使用的一种独特的编码方式,主要用于产生Bitcoin的钱包地址。
相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+"和"/"符号。
设计Base58主要的目的是:
- 避免混淆。在某些字体下,数字0和字母大写O,以及字母大写I和字母小写l会非常相似。
- 不使用"+"和"/"的原因是非字母或数字的字符串作为帐号较难被接受。
- 没有标点符号,通常不会被从中间分行。
- 大部分的软件支持双击选择整个字符串。
但是这个base58的计算量比base64的计算量多了很多。因为58不是2的整数倍,需要不断用除法去计算。
而且长度也比base64稍微多了一点。
C#的实现跨越参考 https://github.com/adamcaudill/Base58Check
代码如下:需要添加System.Numerics;
/// <summary>
/// Base58Check Encoding / Decoding (Bitcoin-style)
/// </summary>
/// <remarks>
/// See here for more details: https://en.bitcoin.it/wiki/Base58Check_encoding
/// </remarks>
public static class Base58CheckEncoding
{
private const int CHECK_SUM_SIZE = 4;
private const string DIGITS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
/// <summary>
/// Encodes data with a 4-byte checksum
/// </summary>
/// <param name="data">Data to be encoded</param>
/// <returns></returns>
public static string Encode(byte[] data)
{
return EncodePlain(_AddCheckSum(data));
}
/// <summary>
/// Encodes data in plain Base58, without any checksum.
/// </summary>
/// <param name="data">The data to be encoded</param>
/// <returns></returns>
public static string EncodePlain(byte[] data)
{
// Decode byte[] to BigInteger
var intData = data.Aggregate<byte, BigInteger>(0, (current, t) => current * 256 + t);
// Encode BigInteger to Base58 string
var result = string.Empty;
while (intData > 0)
{
var remainder = (int)(intData % 58);
intData /= 58;
result = DIGITS[remainder] + result;
}
// Append `1` for each leading 0 byte
for (var i = 0; i < data.Length && data[i] == 0; i++)
{
result = '1' + result;
}
return result;
}
/// <summary>
/// Decodes data in Base58Check format (with 4 byte checksum)
/// </summary>
/// <param name="data">Data to be decoded</param>
/// <returns>Returns decoded data if valid; throws FormatException if invalid</returns>
public static byte[] Decode(string data)
{
var dataWithCheckSum = DecodePlain(data);
var dataWithoutCheckSum = _VerifyAndRemoveCheckSum(dataWithCheckSum);
if (dataWithoutCheckSum == null)
{
throw new FormatException("Base58 checksum is invalid");
}
return dataWithoutCheckSum;
}
/// <summary>
/// Decodes data in plain Base58, without any checksum.
/// </summary>
/// <param name="data">Data to be decoded</param>
/// <returns>Returns decoded data if valid; throws FormatException if invalid</returns>
public static byte[] DecodePlain(string data)
{
// Decode Base58 string to BigInteger
BigInteger intData = 0;
for (var i = 0; i < data.Length; i++)
{
var digit = DIGITS.IndexOf(data[i]); //Slow
if (digit < 0)
{
throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", data[i], i));
}
intData = intData * 58 + digit;
}
// Encode BigInteger to byte[]
// Leading zero bytes get encoded as leading `1` characters
var leadingZeroCount = data.TakeWhile(c => c == '1').Count();
var leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount);
var bytesWithoutLeadingZeros =
intData.ToByteArray()
.Reverse()// to big endian
.SkipWhile(b => b == 0);//strip sign byte
var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray();
return result;
}
private static byte[] _AddCheckSum(byte[] data)
{
var checkSum = _GetCheckSum(data);
var dataWithCheckSum = ArrayHelpers.ConcatArrays(data, checkSum);
return dataWithCheckSum;
}
//Returns null if the checksum is invalid
private static byte[] _VerifyAndRemoveCheckSum(byte[] data)
{
var result = ArrayHelpers.SubArray(data, 0, data.Length - CHECK_SUM_SIZE);
var givenCheckSum = ArrayHelpers.SubArray(data, data.Length - CHECK_SUM_SIZE);
var correctCheckSum = _GetCheckSum(result);
return givenCheckSum.SequenceEqual(correctCheckSum) ? result : null;
}
private static byte[] _GetCheckSum(byte[] data)
{
SHA256 sha256 = new SHA256Managed();
var hash1 = sha256.ComputeHash(data);
var hash2 = sha256.ComputeHash(hash1);
var result = new byte[CHECK_SUM_SIZE];
Buffer.BlockCopy(hash2, 0, result, 0, result.Length);
return result;
}
}
internal class ArrayHelpers
{
public static T[] ConcatArrays<T>(params T[][] arrays)
{
var result = new T[arrays.Sum(arr => arr.Length)];
var offset = 0;
foreach (var arr in arrays)
{
Buffer.BlockCopy(arr, 0, result, offset, arr.Length);
offset += arr.Length;
}
return result;
}
public static T[] ConcatArrays<T>(T[] arr1, T[] arr2)
{
var result = new T[arr1.Length + arr2.Length];
Buffer.BlockCopy(arr1, 0, result, 0, arr1.Length);
Buffer.BlockCopy(arr2, 0, result, arr1.Length, arr2.Length);
return result;
}
public static T[] SubArray<T>(T[] arr, int start, int length)
{
var result = new T[length];
Buffer.BlockCopy(arr, start, result, 0, length);
return result;
}
public static T[] SubArray<T>(T[] arr, int start)
{
return SubArray(arr, start, arr.Length - start);
}
}
python:
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
__b58base = len(__b58chars)
def b58encode(v):
""" encode v, which is a string of bytes, to base58.
"""
long_value = int(v.encode("hex_codec"), 16)
result = ''
while long_value >= __b58base:
div, mod = divmod(long_value, __b58base)
result = __b58chars[mod] + result
long_value = div
result = __b58chars[long_value] + result
# Bitcoin does a little leading-zero-compression:
# leading 0-bytes in the input become leading-1s
nPad = 0
for c in v:
if c == '\0':
nPad += 1
else:
break
return (__b58chars[0] * nPad) + result
def b58decode(v):
""" decode v into a string of len bytes
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += __b58chars.find(c) * (__b58base ** i)
result = ''
while long_value >= 256:
div, mod = divmod(long_value, 256)
result = chr(mod) + result
long_value = div
result = chr(long_value) + result
nPad = 0
for c in v:
if c == __b58chars[0]:
nPad += 1
else:
break
result = chr(0) * nPad + result
return result
if __name__ == "__main__":
print b58encode("hello world")
print b58decode("StV1DL6CwTryKyV")