Take the 2-minute tour ×
Bitcoin Stack Exchange is a question and answer site for Bitcoin crypto-currency enthusiasts. It's 100% free, no registration required.

UPDATE: The code in this question works for all test cases (Hurray!) however I don't like how GetCompact uses the Math.Abs function and don't think it's true to the OpenSSL implementation.

Fixing this issue will probably make this implementation "perfect"

The core issue (I believe) is when .NET does a bit-shift of a negative number, it expands the negative number and because it's stored in Two's compliment, the left shift extra bytes are all ones.

C++ likely does something different when left shifting a BigInteger, because according to the C++ spec, left shifting negative numbers is undefined.

The solution is to use the corresponding multiplication or division instead of a shift. ... I'm not sure how to do that, so your help would be appreciated.


I'm working on the following C# code, and tried to make it faithful to the original C++ source. And I'm trying to get this code to match the unit tests described here.

My goal is not only to have a .NET representation of the QT data structures, but also to read and parse JSON-RPC code as well.

C# Tests

       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# code

   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();
    }

}
share|improve this question
    
Can you post a failing testcase? –  Nick ODell Mar 4 '13 at 4:28
    
@NickODell Lots of tests are failing... I can't figure out why –  makerofthings7 Mar 4 '13 at 4:40
    
Perhaps OpenSSL bitshifting a number != .NET's implementation of bit shifting the same number –  makerofthings7 Mar 4 '13 at 4:58
    
Perhaps an endianness issue. I don't know what endianness bitcoin uses, but you use native endianness. Using native endianness on a platform independent file format is always a bug, even if it might work with your current platform. Edit: Oh it's one of MS's great documentation cases which leads to a contradiction for big-endian architectures. –  CodesInChaos Mar 12 '13 at 8:04

1 Answer 1

Here's one thing I'm pretty sure is a bug, in your compact to BigInteger method:

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

Ought to be:

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

EDIT

Found another, in your BigInteger to compact method:

        ret = ret << (int)amountToShift2;

Here's the thing: ret is never used anywhere else. I'm fairly sure you meant to assign to ret2, and cast to an unsigned integer. Remember to fix both instances of this.

Lemme know if those two suggestions don't fix things.

share|improve this answer
    
Hmm .NET has a bug in ToString("x")... that was throwing me off too –  makerofthings7 Mar 4 '13 at 12:58

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.