The problem
SetCompact as currently implemented interprets the compact number 0x01810000 as -1 instead of 0x81, because it is implemented using BN_mpi2bn, which interprets the highest bit in the first byte of its input as sign and this is not compensated for. — Currently this is not a problem as none of the bitcoin-implementations I've looked at generate compact numbers with the 0x00800000 bit set. Many contain a matching bug in their compact encoding routines.
Satoshi:
CBigNum.GetCompact(0x81)=>0x02008100CBigNum.SetCompact(0x01810000)=>-1Defensive encoding. Decoding results in negative number with highest bit stripped.
bitcoinj:
No compact encoding.
Utils.decodeCompactBits(0x01810000)=>-1Decoding will lead to
Block::getDifficultyTargetAsInteger()throwing aVerificationException.
bitcoinjs:
encodeDiffBits(0x80)=>0x02008000decodeDiffBits(0x01810000)=>0x81(OK)Defensive encoding and correct decoding
libbitcoin:
big_number::compact(0x80)=>0x02008000big_number::set_compact(0x01810000)=>-1Defensive encoding. Decoding results in block_work() returning 0 and validate_block::check_proof_of_work returning false.
This branch
The first commit adds several test cases for GetCompact() and SetCompact(), including one showcasing the erroneous decoding.
The second commit reimplements GetCompact() and SetCompact() using shifts instead of going through the MPI-representation.
GetCompact() now is careful not to generate compact numbers with the 0x00800000 bit set (GetCompact(0xff) yields 0x0200ff00 instead of 0x01ff0000). It results in identical compact numbers for positive inputs as the old code.
SetCompact now interprets the compact number 0x01810000 as 0x81 instead of -1.