Correctness fix: compare nBits directly not in BigInteger form as nBits has multiple non-canonical encodings.

This commit is contained in:
Mike Hearn 2014-05-21 15:09:52 +02:00
parent 4a5854a599
commit ed2948cef1
3 changed files with 51 additions and 10 deletions

View File

@ -854,15 +854,16 @@ public abstract class AbstractBlockChain {
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
BigInteger receivedDifficulty = nextBlock.getDifficultyTargetAsInteger();
long receivedDifficultyCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newDifficulty = newDifficulty.and(mask);
long newDifficultyCompact = Utils.encodeCompactBits(newDifficulty);
if (newDifficulty.compareTo(receivedDifficulty) != 0)
if (newDifficultyCompact != receivedDifficultyCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
receivedDifficulty.toString(16) + " vs " + newDifficulty.toString(16));
newDifficultyCompact + " vs " + receivedDifficultyCompact);
}
private void checkTestnetDifficulty(StoredBlock storedPrev, Block prev, Block next) throws VerificationException, BlockStoreException {

View File

@ -434,8 +434,19 @@ public class Utils {
}
}
// The representation of nBits uses another home-brew encoding, as a way to represent a large
// hash value in only 32 bits.
/**
* <p>The "compact" format is a representation of a whole number N using an unsigned 32 bit number similar to a
* floating point format. The most significant 8 bits are the unsigned exponent of base 256. This exponent can
* be thought of as "number of bytes of N". The lower 23 bits are the mantissa. Bit number 24 (0x800000) represents
* the sign of N. Therefore, N = (-1^sign) * mantissa * 256^(exponent-3).</p>
*
* <p>Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). MPI uses the most significant bit of the
* first byte as sign. Thus 0x1234560000 is compact 0x05123456 and 0xc0de000000 is compact 0x0600c0de. Compact
* 0x05c0de00 would be -0x40de000000.</p>
*
* <p>Bitcoin only uses this "compact" format for encoding difficulty targets, which are unsigned 256bit quantities.
* Thus, all the complexities of the sign bit and using base 256 are probably an implementation accident.</p>
*/
public static BigInteger decodeCompactBits(long compact) {
int size = ((int) (compact >> 24)) & 0xFF;
byte[] bytes = new byte[4 + size];
@ -446,6 +457,27 @@ public class Utils {
return decodeMPI(bytes, true);
}
/**
* @see Utils#decodeCompactBits(long)
*/
public static long encodeCompactBits(BigInteger value) {
long result;
int size = value.toByteArray().length;
if (size <= 3)
result = value.longValue() << 8 * (3 - size);
else
result = value.shiftRight(8 * (size - 3)).longValue();
// The 0x00800000 bit denotes the sign.
// Thus, if it is already set, divide the mantissa by 256 and increase the exponent.
if ((result & 0x00800000L) != 0) {
result >>= 8;
size++;
}
result |= size << 24;
result |= value.signum() == -1 ? 0x00800000 : 0;
return result;
}
/**
* If non-null, overrides the return value of now().
*/

View File

@ -105,15 +105,15 @@ public class UtilsTest {
@Test
public void testReverseBytes() {
Assert.assertArrayEquals(new byte[] {1,2,3,4,5}, Utils.reverseBytes(new byte[] {5,4,3,2,1}));
assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, Utils.reverseBytes(new byte[]{5, 4, 3, 2, 1}));
}
@Test
public void testReverseDwordBytes() {
Assert.assertArrayEquals(new byte[] {1,2,3,4,5,6,7,8}, Utils.reverseDwordBytes(new byte[] {4,3,2,1,8,7,6,5}, -1));
Assert.assertArrayEquals(new byte[] {1,2,3,4}, Utils.reverseDwordBytes(new byte[] {4,3,2,1,8,7,6,5}, 4));
Assert.assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[] {4,3,2,1,8,7,6,5}, 0));
Assert.assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[0], 0));
assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8}, Utils.reverseDwordBytes(new byte[]{4, 3, 2, 1, 8, 7, 6, 5}, -1));
assertArrayEquals(new byte[]{1, 2, 3, 4}, Utils.reverseDwordBytes(new byte[]{4, 3, 2, 1, 8, 7, 6, 5}, 4));
assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[]{4, 3, 2, 1, 8, 7, 6, 5}, 0));
assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[0], 0));
}
@Test
@ -124,4 +124,12 @@ public class UtilsTest {
assertEquals(1, Utils.maxOfMostFreq(1, 1, 2, 2, 1));
assertEquals(-1, Utils.maxOfMostFreq(-1, -1, 2, 2, -1));
}
@Test
public void compactEncoding() throws Exception {
assertEquals(new BigInteger("1234560000", 16), Utils.decodeCompactBits(0x05123456L));
assertEquals(new BigInteger("c0de000000", 16), Utils.decodeCompactBits(0x0600c0de));
assertEquals(0x05123456L, Utils.encodeCompactBits(new BigInteger("1234560000", 16)));
assertEquals(0x0600c0deL, Utils.encodeCompactBits(new BigInteger("c0de000000", 16)));
}
}