¿Cómo codifico / decodifico Base58 Checked una dirección en C#? ¿Qué significa "normalizar los ceros iniciales"?

Estoy tratando de codificar y decodificar una dirección Base58 en C#. La siguiente función es un comienzo, pero tiene algunos problemas:

  • Este código no normaliza los ceros iniciales (¿cómo se ve eso?)

  • Si este método se llama repetidamente en rápida sucesión, se crearán muchos objetos de cadena, lo que ejercerá presión sobre el GC.

Código .NET 4.5

Tenga en cuenta agregar una referencia a System.Numerics

BigInteger  bi =  System.Numerics.BigInteger.Parse("00010966776006953D5567439E5E39F86A0D273BEED61967F6", NumberStyles.HexNumber);

string b58 = EncodeBase58(bi);
Console.WriteLine(b58 + Environment.NewLine + "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM");

   /// .... SNIP

   public static String sBase58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
    public static String EncodeBase58(BigInteger numberToShorten)
    {
        // WARNING: Beware of bignumber implementations that clip leading 0x00 bytes, or prepend extra 0x00 
        // bytes to indicate sign - your code must handle these cases properly or else you may generate valid-looking
        // addresses which can be sent to, but cannot be spent from - which would lead to the permanent loss of coins.)


        // Base58Check encoding is also used for encoding private keys in the Wallet Import Format. This is formed exactly
        // the same as a Bitcoin address, except that 0x80 is used for the version/application byte, and the payload is 32 bytes
        // instead of 20 (a private key in Bitcoin is a single 32-byte unsigned big-endian integer). Such encodings will always
        // yield a 51-character string that starts with '5', or more specifically, either '5H', '5J', or '5K'.   https://en.bitcoin.it/wiki/Base58Check_encoding
        const int sizeWalletImportFormat = 51;

        char[] result = new char[33];

        int i = 0;
        while (numberToShorten >= 0 && result.Length > i)
        {
            var lNumberRemainder = BigInteger.Remainder(numberToShorten, (BigInteger)sBase58Alphabet.Length);
            numberToShorten = numberToShorten / (BigInteger)sBase58Alphabet.Length;
           result[result.Length - 1- i] = sBase58Alphabet[(int)lNumberRemainder] ;
           i++;
        }

        return new string(result);
    }
    //public static long DecodeBase58(String base58StringToExpand)
    //{
    //    long lConverted = 0;
    //    long lTemporaryNumberConverter = 1;

    //    while (base58StringToExpand.Length > 0)
    //    {
    //        String sCurrentCharacter = base58StringToExpand.Substring(base58StringToExpand.Length - 1);
    //        lConverted = lConverted + (lTemporaryNumberConverter * sBase58Alphabet.IndexOf(sCurrentCharacter));
    //        lTemporaryNumberConverter = lTemporaryNumberConverter * sBase58Alphabet.Length;
    //        base58StringToExpand = base58StringToExpand.Substring(0, base58StringToExpand.Length - 1);
    //    }
    //}
Tenga en cuenta que puede usar BigInteger.DivRem para dividir y obtener el resto en una operación que es más rápida

Respuestas (1)

Para normalizar los ceros iniciales se documenta en este enlace, a saber, este texto en la parte inferior:

En una conversión base estándar, el byte 0x00 a la izquierda sería irrelevante (como escribir 052 en lugar de 52), pero en la red BTC, los caracteres cero de la izquierda se llevan a través de la conversión. Entonces, por cada byte 0x00 en el extremo izquierdo de la dirección binaria, adjuntaremos un carácter '1' a la dirección Base58. Esta es la razón por la que todas las direcciones de la red principal comienzan con 1.

El siguiente código abordará el problema de GC con la asignación excesiva y el descarte del tipo String, sin embargo, no maneja correctamente los bytes cero iniciales. Necesito encontrar una lista de longitudes esperadas por tipo o algún otro enfoque.

    public static String sBase58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
    public static String EncodeBase58(BigInteger numberToShorten)
    {
        // WARNING: Beware of bignumber implementations that clip leading 0x00 bytes, or prepend extra 0x00 
        // bytes to indicate sign - your code must handle these cases properly or else you may generate valid-looking
        // addresses which can be sent to, but cannot be spent from - which would lead to the permanent loss of coins.)


        // Base58Check encoding is also used for encoding private keys in the Wallet Import Format. This is formed exactly
        // the same as a Bitcoin address, except that 0x80 is used for the version/application byte, and the payload is 32 bytes
        // instead of 20 (a private key in Bitcoin is a single 32-byte unsigned big-endian integer). Such encodings will always
        // yield a 51-character string that starts with '5', or more specifically, either '5H', '5J', or '5K'.   https://en.bitcoin.it/wiki/Base58Check_encoding
        const int sizeWalletImportFormat = 51;

        char[] result = new char[33];

        Int32 iAlphabetLength = sBase58Alphabet.Length;
        BigInteger iAlphabetLength2 = BigInteger.Parse(iAlphabetLength.ToString());

        int i = 0;
        while (numberToShorten >= 0 && result.Length > i)
        {
            var lNumberRemainder = BigInteger.Remainder(numberToShorten, iAlphabetLength2);
            numberToShorten = numberToShorten / iAlphabetLength;
           result[result.Length - 1- i] = sBase58Alphabet[(int)lNumberRemainder] ;
           i++;
        }

        return new string(result);
    }