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