diff --git a/src/main/java/org/bitcoinj/core/AuxPoW.java b/src/main/java/org/bitcoinj/core/AuxPoW.java index 00eaf5e8..0d1ac04e 100644 --- a/src/main/java/org/bitcoinj/core/AuxPoW.java +++ b/src/main/java/org/bitcoinj/core/AuxPoW.java @@ -41,6 +41,12 @@ public class AuxPoW extends ChildMessage { (byte) 0xfa, (byte) 0xbe, "m".getBytes()[0], "m".getBytes()[0] }; + /** + * Maximum index of the merkle root hash in the coinbase transaction script, + * where no merged mining header is present. + */ + protected static final int MAX_INDEX_PC_BACKWARDS_COMPATIBILITY = 20; + private static final Logger log = LoggerFactory.getLogger(AuxPoW.class); private static final long serialVersionUID = -8567546957352643140L; @@ -290,44 +296,49 @@ public class AuxPoW extends ChildMessage { // Check that the same work is not submitted twice to our chain, by // confirming that the child block hash is in the coinbase merkle tree - int pcHeader = -1; + int pcHead = -1; int pc = -1; for (int scriptIdx = 0; scriptIdx < script.length; scriptIdx++) { if (arrayMatch(script, scriptIdx, MERGED_MINING_HEADER)) { // Enforce only one chain merkle root by checking that a single instance of the merged // mining header exists just before. - if (pcHeader >= 0) { + if (pcHead >= 0) { if (throwException) { throw new VerificationException("Multiple merged mining headers in coinbase"); } return false; } - pcHeader = scriptIdx; + pcHead = scriptIdx; } else if (arrayMatch(script, scriptIdx, vchRootHash)) { pc = scriptIdx; } } - if (-1 == pcHeader) { - if (throwException) { - throw new VerificationException("MergedMiningHeader missing from parent coinbase"); - } - return false; - } - - if (-1 == pc) { + if (pc == -1) { if (throwException) { throw new VerificationException("Aux POW missing chain merkle root in parent coinbase"); } return false; } - if (pcHeader + MERGED_MINING_HEADER.length != pc) { - if (throwException) { - throw new VerificationException("Merged mining header is not just before chain merkle root"); + if (pcHead != -1) { + if (pcHead + MERGED_MINING_HEADER.length != pc) { + if (throwException) { + throw new VerificationException("Merged mining header is not just before chain merkle root"); + } + return false; + } + } else { + // For backward compatibility. + // Enforce only one chain merkle root by checking that it starts early in the coinbase. + // 8-12 bytes are enough to encode extraNonce and nBits. + if (pc > MAX_INDEX_PC_BACKWARDS_COMPATIBILITY) { + if (throwException) { + throw new VerificationException("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase"); + } + return false; } - return false; } // Ensure we are at a deterministic point in the merkle leaves by hashing @@ -361,12 +372,12 @@ public class AuxPoW extends ChildMessage { return false; } - BigInteger h = altcoinParams.getBlockDifficultyHash(getParentBlockHeader()) - .toBigInteger(); - if (h.compareTo(target) > 0) { + Sha256Hash hash = altcoinParams.getBlockDifficultyHash(getParentBlockHeader()); + BigInteger hashVal = hash.toBigInteger(); + if (hashVal.compareTo(target) > 0) { // Proof of work check failed! if (throwException) { - throw new VerificationException("Hash is higher than target: " + getParentBlockHeader().getHashAsString() + " vs " + throw new VerificationException("Hash is higher than target: " + hash.toString() + " vs " + target.toString(16)); } return false; diff --git a/src/test/java/org/bitcoinj/core/AuxPoWTest.java b/src/test/java/org/bitcoinj/core/AuxPoWTest.java index ac4b9208..9508ab8e 100644 --- a/src/test/java/org/bitcoinj/core/AuxPoWTest.java +++ b/src/test/java/org/bitcoinj/core/AuxPoWTest.java @@ -1,17 +1,13 @@ package org.bitcoinj.core; -import java.io.OutputStream; -import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; -import static org.bitcoinj.core.AuxPoW.MERGED_MINING_HEADER; import org.libdohj.core.AltcoinSerializer; -import org.libdohj.core.AuxPoWNetworkParameters; import org.libdohj.params.DogecoinMainNetParams; +import org.libdohj.params.DogecoinTestNet3Params; import static org.bitcoinj.core.Util.getBytes; -import static org.bitcoinj.core.Utils.reverseBytes; import static org.junit.Assert.*; import org.junit.Before; @@ -74,6 +70,26 @@ public class AuxPoWTest { auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"), Utils.decodeCompactBits(0x1b06f8f0), true); } + + /** + * Validate the AuxPoW header with no explicit data header in the coinbase + * transaction. Namecoin block #19,414 + */ + @Test + public void checkAuxPoWHeaderNoTxHeader() throws Exception { + // Emulate Namecoin block hashing for this test + final NetworkParameters namecoinLikeParams = new DogecoinTestNet3Params() { + @Override + public Sha256Hash getBlockDifficultyHash(Block block) { + // Namecoin uses SHA256 hashes + return block.getHash(); + } + }; + byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header_no_tx_header.bin")); + AuxPoW auxpow = new AuxPoW(namecoinLikeParams, auxpowAsBytes, (ChildMessage) null, namecoinLikeParams.getDefaultSerializer()); + auxpow.checkProofOfWork(Sha256Hash.wrap("5fb89c3b18c27bc38d351d516177cbd3504c95ca0494cbbbbd52f2fb5f2ff1ec"), + Utils.decodeCompactBits(0x1b00b269), true); + } @Rule public ExpectedException expectedEx = ExpectedException.none(); @@ -164,8 +180,9 @@ public class AuxPoWTest { } /** - * Catch the case that the merged mine header is missing from the coinbase - * transaction. + * Catch the case that the coinbase transaction does not contain details of + * the merged block. In this case we make the transaction script too short + * for it to do so. */ @Test public void shouldRejectIfMergedMineHeaderMissing() throws Exception { @@ -175,11 +192,14 @@ public class AuxPoWTest { // This will also break the difficulty check, but as that doesn't occur // until the end, we can get away with it. final TransactionInput in = auxpow.getCoinbase().getInput(0); - in.getScriptBytes()[4] = 0; // Break the first byte of the header + final byte[] paddedScriptBytes = new byte[in.getScriptBytes().length + (AuxPoW.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4)]; + Arrays.fill(paddedScriptBytes, (byte) 0); + System.arraycopy(in.getScriptBytes(), 8, paddedScriptBytes, (AuxPoW.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4), in.getScriptBytes().length - 8); + in.setScriptBytes(paddedScriptBytes); updateMerkleRootToMatchCoinbase(auxpow); expectedEx.expect(org.bitcoinj.core.VerificationException.class); - expectedEx.expectMessage("MergedMiningHeader missing from parent coinbase"); + expectedEx.expectMessage("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase"); auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"), Utils.decodeCompactBits(0x1b06f8f0), true); } @@ -337,7 +357,7 @@ public class AuxPoWTest { final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer()); expectedEx.expect(org.bitcoinj.core.VerificationException.class); - expectedEx.expectMessage("Hash is higher than target: a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c vs 0"); + expectedEx.expectMessage("Hash is higher than target: 000000000003178bb23160cdbc81af53f47cae9f479acf1e69849da42fd5bfca vs 0"); auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"), Utils.decodeCompactBits(0x00), true); diff --git a/src/test/resources/org/bitcoinj/core/auxpow_header_no_tx_header.bin b/src/test/resources/org/bitcoinj/core/auxpow_header_no_tx_header.bin new file mode 100644 index 00000000..fd189bf6 Binary files /dev/null and b/src/test/resources/org/bitcoinj/core/auxpow_header_no_tx_header.bin differ