¿Qué tiene de malo este código C# que convierte los bits de un bloque en un objetivo?

ACTUALIZACIÓN: El código en esta pregunta funciona para todos los casos de prueba (¡Hurra!), Sin embargo, no me gusta cómo GetCompactusa la Math.Absfunción y no creo que sea fiel a la implementación de OpenSSL.

Solucionar este problema probablemente hará que esta implementación sea "perfecta"

El problema central (creo) es cuando .NET hace un cambio de bits de un número negativo, expande el número negativo y debido a que está almacenado en el complemento de Two, los bytes adicionales de desplazamiento a la izquierda son todos unos.

Es probable que C++ haga algo diferente cuando se desplaza a la izquierda un BigInteger, porque de acuerdo con la especificación de C++, los números negativos de desplazamiento a la izquierda no están definidos.

La solución es utilizar la multiplicación o división correspondiente en lugar de un desplazamiento. ... No estoy seguro de cómo hacerlo, por lo que agradecería su ayuda.


Estoy trabajando en el siguiente código de C# y traté de hacerlo fiel a la fuente original de C++. Y estoy tratando de hacer que este código coincida con las pruebas unitarias descritas aquí.

Mi objetivo no es solo tener una representación .NET de las estructuras de datos QT, sino también leer y analizar el código JSON-RPC.

Pruebas de C#

       BigInteger bb =   BitcoinQT.SetCompact(numToCompact);


        bb = BitcoinQT.SetCompact(0x00123456);
       /*
       00000000000100100011010001010110 SetCompact:
       00000000011111111111111111111111 Bitmask & (extract 0..23)
       00000000000100100011010001010110 result
       00000000000000000000000000000000 Read bytes 25..32 (>> 24)
       000100100011010001010110 preshifted 24
       00000000 postshifted 24
       00000000100000000000000000000000 ... check bit is neg
       00000000000000000000000000000000 ... Result

       00000000000000000000000000001100 ERROR RESULT SHOULD BE THIS 
        */

código C#

   class BlockTargetBits
{

   static  bool debug = false;

   internal static string GetCompact(BigInteger originalBigNumber)
   {
       // 
       // 
       // Get Compact
       BigInteger num = originalBigNumber;
       byte[] numAsBytes = num.ToByteArray();
       uint compactBitsRepresentation = 0;
       uint size2;// BN_num_bytes(num);
       size2 = (uint)originalBigNumber.NumberOfBytes();
       if (size2 <= 3)
       {
           uint amountToShift2 = 8 * (3 - size2);
           if (debug) Console.WriteLine(GetBits(num) + " will be shifted " + amountToShift2);
           compactBitsRepresentation = (uint)(int)(BigInteger.Abs(num) << (int)amountToShift2);  // HACK: -- ABS MAY NOT BE THE CORRECT THING TO USE HERE
           if (debug) Console.WriteLine(GetBits(compactBitsRepresentation) + " was shifted " + amountToShift2);
       }
       else
       {
           BigInteger bn = num;
           uint amountToShift2 = 8 * (size2 - 3);
           if (debug) Console.WriteLine(GetBits(bn) + " will be shifted " + amountToShift2);
           var bnShifted = BigInteger.Abs(bn) >> (int)amountToShift2;  // HACK: -- ABS MAY NOT BE THE CORRECT THING TO USE HERE
           compactBitsRepresentation = (uint)bnShifted;
       }

       // The 0x00800000 bit denotes the sign.
       // Thus, if it is already set, divide the mantissa by 256 and increase the exponent.
       Console.WriteLine(compactBitsRepresentation.ToString("x"));
       if ((compactBitsRepresentation & 0x00800000) != 0)
       {
           compactBitsRepresentation >>= 8;
           size2++;
       }
       if (debug) Console.WriteLine(GetBits(size2) + " size ");

       var tmp = size2 << 24;
       if (debug) Console.WriteLine(GetBits(tmp) + " size (shifted to proper postion)");
       compactBitsRepresentation |= size2 << 24;
       if (debug) Console.WriteLine("21 987654321 987654321 987654321");
       if (debug) Console.WriteLine(GetBits(compactBitsRepresentation) + " size # then compact");

       compactBitsRepresentation |= (num.Sign < 0 ? (uint)0x00800000 : 0);

       if (compactBitsRepresentation == 0)
           return "0";
       return "0x" + compactBitsRepresentation.ToString("x8");
   }


     internal static System.Numerics.BigInteger SetCompact(uint numToCompact)
    {
        if (debug)   Console.WriteLine(GetBits(numToCompact) + " This number will be compacted ");

        //
        //  SetCompact
        // Extract the number from bits 0..23
        if (debug)  Console.WriteLine(GetBits(0x007fffff) + " Bitmask & (extract 0..23) ");

        uint nWord = numToCompact & 0x007fffff;
        if (debug)  Console.WriteLine(GetBits(nWord) + " result ");


        BigInteger ret = new BigInteger(nWord);

        // Add zeroes to the left according to bits 25..32
        var ttt =  ret.ToByteArray();

        uint size = numToCompact >> 24;
        if (debug)  Console.WriteLine(GetBits(size) + " Read bytes 25..32 (>> 24) ");


        uint amountToShift = 0;
        if (size <= 3)
        {
            amountToShift = 8 * (3 - size);
            if (debug)  Console.WriteLine(GetBits(ret) + " preshifted " + amountToShift);

            ret = ret >> (int)amountToShift;
            if (debug) Console.WriteLine( GetBits(ret)+ " postshifted " + amountToShift );
        }
        else
        {
            amountToShift = 8 * (size - 3);
            if (debug) Console.WriteLine(GetBits(ret) + " preshifted " + amountToShift);

            ret = ret << (int)amountToShift;

            if (debug)   Console.WriteLine(GetBits(ret) + " shifted " + amountToShift);
        }

        // Set the value negative if required per bit 24
        if (debug) Console.WriteLine(GetBits(0x00800000) + " ... check bit is neg");

        UInt32 isNegative = 0x00800000 & numToCompact;

        if (debug)  Console.WriteLine(GetBits(isNegative) + " ... Result");

        if (isNegative != 0)
            ret = ret * -1;  

        var test = ret.ToByteArray();
        if (debug) Console.WriteLine(ret + " return");
        if (debug) Console.WriteLine();
        return ret;
    }

    internal static string GetHex(BigInteger bb)
    {
        if (bb == 0)
            return "0";
        else 
        return bb.ToSignedHexString().TrimStart("0".ToCharArray());
    }

    public static string GetBits(BigInteger num)
    {
        return GetBits(num.ToByteArray());

    }
    public static string GetBits(int num)
    {
        return GetBits(BitConverter.GetBytes(num));
    }
    public static string GetBits(uint num)
    {
       return  GetBits(BitConverter.GetBytes(num));
    }
    public static string GetBits(byte[] bytes)
    {
        StringBuilder sb = new StringBuilder();


        int bitPos = (8 * bytes.Length) -1;
        while (bitPos >  -1)
        {
            int byteIndex = bitPos / 8;
            int offset = bitPos % 8;
            bool isSet = (bytes[byteIndex] & (1 << offset)) != 0;

            // isSet = [True] if the bit at bitPos is set, false otherwise
            if (isSet)
                sb.Append("1");
            else
                sb.Append("0");
            bitPos--;
        }


        return sb.ToString();
    }

}
¿Puedes publicar un caso de prueba fallido?
@NickODell Muchas pruebas están fallando... No puedo entender por qué
¡Quizás OpenSSL cambiando de bits un número! = Implementación de .NET de cambio de bits del mismo número
Tal vez un problema endianness. No sé qué endianness usa bitcoin, pero tú usas endianness nativo. Usar endianness nativo en un formato de archivo independiente de la plataforma siempre es un error, incluso si podría funcionar con su plataforma actual. Editar: Oh, es uno de los grandes casos de documentación de MS que conduce a una contradicción para las arquitecturas big-endian.

Respuestas (1)

Aquí hay una cosa que estoy bastante seguro de que es un error, en su método compacto a BigInteger:

        ret = ret << (int)amountToShift;
        amountToShift = 8 * (size - 3);

Debería ser:

        amountToShift = 8 * (size - 3);
        ret = ret << (int)amountToShift;

EDITAR

Encontré otro, en su método BigInteger to compact:

        ret = ret << (int)amountToShift2;

Aquí está la cosa: ret nunca se usa en ningún otro lugar. Estoy bastante seguro de que querías asignar a ret2 y convertir a un entero sin signo. Recuerde arreglar ambas instancias de esto.

Déjame saber si esas dos sugerencias no solucionan las cosas.

Hmm .NET tiene un error en ToString("x") ... eso también me estaba desconcertando