From c6659bad128ebc556fef4d4f7e462140f3f4bd9f Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Sun, 31 May 2015 22:54:46 +0100 Subject: [PATCH] Completed AuxPoW header validation code. --- .../params/AbstractDogecoinParams.java | 27 +- .../params/DogecoinMainNetParams.java | 1 - .../java/org/bitcoinj/core/AltcoinBlock.java | 99 +++---- .../core/AltcoinNetworkParameters.java | 34 +++ .../org/bitcoinj/core/AltcoinSerializer.java | 27 ++ src/main/java/org/bitcoinj/core/AuxPoW.java | 266 +++++++++++++++--- .../core/AuxPoWNetworkParameters.java | 16 -- .../java/org/bitcoinj/core/MerkleBranch.java | 44 +-- .../java/org/bitcoinj/core/AuxPoWTest.java | 24 +- .../org/bitcoinj/core/DogecoinBlockTest.java | 15 +- .../org/bitcoinj/core/MerkleBranchTest.java | 5 +- 11 files changed, 401 insertions(+), 157 deletions(-) create mode 100644 src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java create mode 100644 src/main/java/org/bitcoinj/core/AltcoinSerializer.java delete mode 100644 src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java diff --git a/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java b/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java index 21a33aa2..aefad16e 100644 --- a/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java +++ b/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java @@ -20,12 +20,11 @@ import java.io.ByteArrayOutputStream; import java.math.BigInteger; import org.bitcoinj.core.AltcoinBlock; -import org.bitcoinj.core.AuxPoWNetworkParameters; +import org.bitcoinj.core.AltcoinNetworkParameters; import org.bitcoinj.core.Block; import org.bitcoinj.core.Coin; import static org.bitcoinj.core.Coin.COIN; import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.VerificationException; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptOpCodes; @@ -36,7 +35,8 @@ import org.bitcoinj.utils.MonetaryFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.google.common.base.Preconditions.checkState; +import org.bitcoinj.core.AltcoinSerializer; +import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.StoredBlock; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionInput; @@ -46,7 +46,7 @@ import org.bitcoinj.core.Utils; /** * Parameters for the main Dogecoin production network on which people trade goods and services. */ -public abstract class AbstractDogecoinParams extends NetworkParameters implements AuxPoWNetworkParameters { +public abstract class AbstractDogecoinParams extends NetworkParameters implements AltcoinNetworkParameters { /** Standard format for the DOGE denomination. */ public static final MonetaryFormat DOGE; /** Standard format for the mDOGE denomination. */ @@ -55,6 +55,7 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement public static final MonetaryFormat KOINU; public static final int DIGISHIELD_BLOCK_HEIGHT = 145000; // Block height to use Digishield from + public static final int AUXPOW_CHAIN_ID = 0x0062; // 98 public static final int DOGE_TARGET_TIMESPAN = 4 * 60 * 60; // 4 hours per difficulty cycle, on average. public static final int DOGE_TARGET_TIMESPAN_NEW = 60; // 60s per difficulty cycle, on average. Kicks in after block 145k. public static final int DOGE_TARGET_SPACING = 1 * 60; // 1 minute per block. @@ -290,6 +291,24 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement return DIGISHIELD_BLOCK_HEIGHT; } + @Override + public int getChainID() { + return AUXPOW_CHAIN_ID; + } + + /** + * Get the hash to use for a block. + */ + @Override + public Sha256Hash getBlockDifficultyHash(Block block) { + return ((AltcoinBlock) block).getScryptHash(); + } + + @Override + public AltcoinSerializer getSerializer(boolean parseLazy, boolean parseRetain) { + return new AltcoinSerializer(this, parseLazy, parseRetain); + } + @Override public boolean isAuxPoWBlockVersion(long version) { return version >= BLOCK_VERSION_AUXPOW diff --git a/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java b/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java index e7dc446e..28e99fa6 100644 --- a/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java +++ b/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java @@ -17,7 +17,6 @@ package org.altcoinj.params; import org.altcoinj.core.ScryptHash; -import org.bitcoinj.core.AltcoinBlock; import static com.google.common.base.Preconditions.checkState; diff --git a/src/main/java/org/bitcoinj/core/AltcoinBlock.java b/src/main/java/org/bitcoinj/core/AltcoinBlock.java index 8d2ffcf6..262c304a 100644 --- a/src/main/java/org/bitcoinj/core/AltcoinBlock.java +++ b/src/main/java/org/bitcoinj/core/AltcoinBlock.java @@ -59,50 +59,30 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { super(params); } - /** Constructs a block object from the Bitcoin wire format. */ - public AltcoinBlock(NetworkParameters params, byte[] payloadBytes) throws ProtocolException { - super(params, payloadBytes); + /** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */ + public AltcoinBlock(NetworkParameters params, byte[] payloadBytes) { + this(params, payloadBytes, params.getDefaultSerializer(), payloadBytes.length); } /** - * Contruct a block object from the Bitcoin wire format. + * Construct a block object from the Bitcoin wire format. * @param params NetworkParameters object. - * @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. - * @param parseRetain Whether to retain the backing byte array for quick reserialization. - * If true and the backing byte array is invalidated due to modification of a field then - * the cached bytes may be repopulated and retained if the message is serialized again in the future. + * @param serializer the serializer to use for this message. * @param length The length of message if known. Usually this is provided when deserializing of the wire * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH * @throws ProtocolException */ - public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, boolean parseLazy, boolean parseRetain, int length) + public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, MessageSerializer serializer, int length) throws ProtocolException { - super(params, payloadBytes, parseLazy, parseRetain, length); + super(params, payloadBytes, serializer, length); } - /** - * Contruct a block object from the Bitcoin wire format. Used in the case of a block - * contained within another message (i.e. for AuxPoW header). - * - * @param params NetworkParameters object. - * @param payloadBytes Bitcoin protocol formatted byte array containing message content. - * @param offset The location of the first payload byte within the array. - * @param parent The message element which contains this block, maybe null for no parent. - * @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. - * @param parseRetain Whether to retain the backing byte array for quick reserialization. - * If true and the backing byte array is invalidated due to modification of a field then - * the cached bytes may be repopulated and retained if the message is serialized again in the future. - * @param length The length of message if known. Usually this is provided when deserializing of the wire - * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH - * @throws ProtocolException - */ - public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, int offset, @Nullable Message parent, boolean parseLazy, boolean parseRetain, int length) - throws ProtocolException { - // TODO: Keep the parent - super(params, payloadBytes, offset, parent, parseLazy, parseRetain, length); + public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, int offset, + Message parent, MessageSerializer serializer, int length) + throws ProtocolException { + super(params, payloadBytes, serializer, length); } - /** * Construct a block initialized with all the given fields. * @param params Which network the block is for. @@ -158,18 +138,18 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { return; this.auxpow = null; - if (this.params instanceof AuxPoWNetworkParameters) { - final AuxPoWNetworkParameters altcoinParams = (AuxPoWNetworkParameters)this.params; + if (this.params instanceof AltcoinNetworkParameters) { + final AltcoinNetworkParameters altcoinParams = (AltcoinNetworkParameters)this.params; if (altcoinParams.isAuxPoWBlockVersion(this.getVersion())) { // The following is used in dogecoinj, but I don't think we necessarily need it // payload.length >= 160) { // We have at least 2 headers in an Aux block. Workaround for StoredBlocks - this.auxpow = new AuxPoW(params, payload, cursor, this, parseLazy, parseRetain); + this.auxpow = new AuxPoW(params, payload, cursor, this, serializer); optimalEncodingMessageSize += auxpow.getOptimalEncodingMessageSize(); } } this.auxpowParsed = true; - this.auxpowBytesValid = parseRetain; + this.auxpowBytesValid = serializer.isParseRetainMode(); } @Override @@ -194,22 +174,24 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { // Ignore the header since it has fixed length. If length is not provided we will have to // invoke a light parse of transactions to calculate the length. if (length == UNKNOWN_LENGTH) { - Preconditions.checkState(parseLazy, + Preconditions.checkState(serializer.isParseLazyMode(), "Performing lite parse of block transaction as block was initialised from byte array " + "without providing length. This should never need to happen."); parseAuxPoW(); parseTransactions(); length = cursor - offset; } else { - transactionBytesValid = !transactionsParsed || parseRetain && length > HEADER_SIZE; + transactionBytesValid = !transactionsParsed || serializer.isParseRetainMode() && length > HEADER_SIZE; } - headerBytesValid = !headerParsed || parseRetain && length >= HEADER_SIZE; + headerBytesValid = !headerParsed || serializer.isParseRetainMode() && length >= HEADER_SIZE; } @Override void writeHeader(OutputStream stream) throws IOException { super.writeHeader(stream); - // TODO: Write the AuxPoW header + if (null != this.auxpow) { + this.auxpow.bitcoinSerialize(stream); + } } /** Returns a copy of the block, but without any transactions. */ @@ -223,28 +205,27 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { /** Returns true if the hash of the block is OK (lower than difficulty target). */ protected boolean checkProofOfWork(boolean throwException) throws VerificationException { - // TODO: Add AuxPoW support + if (params instanceof AltcoinNetworkParameters) { + BigInteger target = getDifficultyTargetAsInteger(); + + final AltcoinNetworkParameters altParams = (AltcoinNetworkParameters)auxpow; + if (altParams.isAuxPoWBlockVersion(getVersion()) && null != auxpow) { + return auxpow.checkProofOfWork(this.getHash(), target, throwException); + } - // This part is key - it is what proves the block was as difficult to make as it claims - // to be. Note however that in the context of this function, the block can claim to be - // as difficult as it wants to be .... if somebody was able to take control of our network - // connection and fork us onto a different chain, they could send us valid blocks with - // ridiculously easy difficulty and this function would accept them. - // - // To prevent this attack from being possible, elsewhere we check that the difficultyTarget - // field is of the right value. This requires us to have the preceeding blocks. - BigInteger target = getDifficultyTargetAsInteger(); - - BigInteger h = getHash().toBigInteger(); - if (h.compareTo(target) > 0) { - // Proof of work check failed! - if (throwException) - throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs " - + target.toString(16)); - else - return false; + BigInteger h = altParams.getBlockDifficultyHash(this).toBigInteger(); + if (h.compareTo(target) > 0) { + // Proof of work check failed! + if (throwException) + throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs " + + target.toString(16)); + else + return false; + } + return true; + } else { + return super.checkProofOfWork(throwException); } - return true; } /** diff --git a/src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java b/src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java new file mode 100644 index 00000000..4a67f933 --- /dev/null +++ b/src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Ross Nicoll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bitcoinj.core; + +/** + * + * @author jrn + */ +public interface AltcoinNetworkParameters { + + boolean isAuxPoWBlockVersion(long version); + + int getChainID(); + + /** + * Get the hash for the given block, for comparing against target difficulty. + * This provides an extension hook for networks which use a hash other than + * SHA256 twice (Bitcoin standard) for proof of work. + */ + Sha256Hash getBlockDifficultyHash(Block block); +} diff --git a/src/main/java/org/bitcoinj/core/AltcoinSerializer.java b/src/main/java/org/bitcoinj/core/AltcoinSerializer.java new file mode 100644 index 00000000..dec3ab19 --- /dev/null +++ b/src/main/java/org/bitcoinj/core/AltcoinSerializer.java @@ -0,0 +1,27 @@ + /* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.bitcoinj.core; + +/** + * + * @author jrn + */ +public class AltcoinSerializer extends BitcoinSerializer { + + public AltcoinSerializer(NetworkParameters params, boolean parseLazy, boolean parseRetain) { + super(params, parseLazy, parseRetain); + } + + @Override + public Block makeBlock(byte[] payloadBytes) throws ProtocolException { + return new AltcoinBlock(getParameters(), payloadBytes, this, payloadBytes.length); + } + + @Override + public Block makeBlock(byte[] payloadBytes, int length) throws ProtocolException { + return new AltcoinBlock(getParameters(), payloadBytes, this, length); + } +} diff --git a/src/main/java/org/bitcoinj/core/AuxPoW.java b/src/main/java/org/bitcoinj/core/AuxPoW.java index 90327df1..6085c920 100644 --- a/src/main/java/org/bitcoinj/core/AuxPoW.java +++ b/src/main/java/org/bitcoinj/core/AuxPoW.java @@ -23,23 +23,31 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.*; +import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.*; /** *

An AuxPoW header wraps a block header from another coin, enabling the foreign - * chain's proof of work to be used for this chain as well.

+ * chain's proof of work to be used for this chain as well. Note: + * NetworkParameters for AuxPoW networks must implement AltcoinNetworkParameters + * in order for AuxPoW to work.

*/ public class AuxPoW extends ChildMessage implements Serializable { - + + public static final byte[] MERGED_MINING_HEADER = new byte[] { + (byte) 0xfa, (byte) 0xbe, "m".getBytes()[0], "m".getBytes()[0] + }; + private static final Logger log = LoggerFactory.getLogger(AuxPoW.class); private static final long serialVersionUID = -8567546957352643140L; - private Transaction transaction; + private Transaction transaction; private Sha256Hash hashBlock; private MerkleBranch coinbaseBranch; - private MerkleBranch blockchainBranch; - private Block parentBlockHeader; + private MerkleBranch chainMerkleBranch; + private AltcoinBlock parentBlockHeader; // Transactions can be encoded in a way that will use more bytes than is optimal // (due to VarInts having multiple encodings) @@ -50,36 +58,38 @@ public class AuxPoW extends ChildMessage implements Serializable { public AuxPoW(NetworkParameters params, @Nullable Message parent) { super(params); - transaction = new Transaction(params); - hashBlock = Sha256Hash.ZERO_HASH; - coinbaseBranch = new MerkleBranch(params, this); - blockchainBranch = new MerkleBranch(params, this); - parentBlockHeader = null; + transaction = new Transaction(params); + hashBlock = Sha256Hash.ZERO_HASH; + coinbaseBranch = new MerkleBranch(params, this); + chainMerkleBranch = new MerkleBranch(params, this); + parentBlockHeader = null; } /** * Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed. - * @param params NetworkParameters object.1 + * @param params NetworkParameters object. * @param payload Bitcoin protocol formatted byte array containing message content. * @param offset The location of the first payload byte within the array. * @param parent The message element which contains this header. - * @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. - * @param parseRetain Whether to retain the backing byte array for quick reserialization. - * If true and the backing byte array is invalidated due to modification of a field then - * the cached bytes may be repopulated and retained if the message is serialized again in the future. + * @param serializer the serializer to use for this message. * @throws ProtocolException */ - public AuxPoW(NetworkParameters params, byte[] payload, int offset, Message parent, boolean parseLazy, boolean parseRetain) + public AuxPoW(NetworkParameters params, byte[] payload, int offset, Message parent, MessageSerializer serializer) throws ProtocolException { - super(params, payload, offset, parent, parseLazy, parseRetain, Message.UNKNOWN_LENGTH); + super(params, payload, offset, parent, serializer, Message.UNKNOWN_LENGTH); } /** * Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed. + * + * @param params NetworkParameters object. + * @param payload Bitcoin protocol formatted byte array containing message content. + * @param parent The message element which contains this header. + * @param serializer the serializer to use for this message. */ - public AuxPoW(NetworkParameters params, byte[] payload, @Nullable Message parent, boolean parseLazy, boolean parseRetain) + public AuxPoW(NetworkParameters params, byte[] payload, @Nullable Message parent, MessageSerializer serializer) throws ProtocolException { - super(params, payload, 0, parent, parseLazy, parseRetain, Message.UNKNOWN_LENGTH); + super(params, payload, 0, parent, serializer, Message.UNKNOWN_LENGTH); } @Override @@ -96,11 +106,11 @@ public class AuxPoW extends ChildMessage implements Serializable { // jump past header hash cursor += 4; - // Coin base branch - cursor += MerkleBranch.calcLength(buf, offset); + // Coin base branch + cursor += MerkleBranch.calcLength(buf, offset); - // Block chain branch - cursor += MerkleBranch.calcLength(buf, offset); + // Block chain branch + cursor += MerkleBranch.calcLength(buf, offset); // Block header cursor += Block.HEADER_SIZE; @@ -115,26 +125,26 @@ public class AuxPoW extends ChildMessage implements Serializable { return; cursor = offset; - transaction = new Transaction(params, payload, cursor, this, parseLazy, parseRetain, Message.UNKNOWN_LENGTH); + transaction = new Transaction(params, payload, cursor, this, serializer, Message.UNKNOWN_LENGTH); cursor += transaction.getOptimalEncodingMessageSize(); optimalEncodingMessageSize = transaction.getOptimalEncodingMessageSize(); hashBlock = readHash(); - optimalEncodingMessageSize += 32; // Add the hash size to the optimal encoding + optimalEncodingMessageSize += 32; // Add the hash size to the optimal encoding - coinbaseBranch = new MerkleBranch(params, this, payload, cursor, parseLazy, parseRetain); - cursor += coinbaseBranch.getOptimalEncodingMessageSize(); - optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize(); + coinbaseBranch = new MerkleBranch(params, this, payload, cursor, serializer); + cursor += coinbaseBranch.getOptimalEncodingMessageSize(); + optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize(); - blockchainBranch = new MerkleBranch(params, this, payload, cursor, parseLazy, parseRetain); - cursor += blockchainBranch.getOptimalEncodingMessageSize(); - optimalEncodingMessageSize += blockchainBranch.getOptimalEncodingMessageSize(); + chainMerkleBranch = new MerkleBranch(params, this, payload, cursor, serializer); + cursor += chainMerkleBranch.getOptimalEncodingMessageSize(); + optimalEncodingMessageSize += chainMerkleBranch.getOptimalEncodingMessageSize(); // Make a copy of JUST the contained block header, so the block parser doesn't try reading // transactions past the end byte[] blockBytes = Arrays.copyOfRange(payload, cursor, cursor + Block.HEADER_SIZE); cursor += Block.HEADER_SIZE; - parentBlockHeader = new AltcoinBlock(params, blockBytes, 0, this, parseLazy, parseRetain, Block.HEADER_SIZE); + parentBlockHeader = new AltcoinBlock(params, blockBytes, 0, this, serializer, Block.HEADER_SIZE); length = cursor - offset; } @@ -164,13 +174,13 @@ public class AuxPoW extends ChildMessage implements Serializable { @Override protected void bitcoinSerializeToStream(OutputStream stream) throws IOException { - transaction.bitcoinSerialize(stream); + transaction.bitcoinSerialize(stream); stream.write(Utils.reverseBytes(hashBlock.getBytes())); - coinbaseBranch.bitcoinSerialize(stream); - blockchainBranch.bitcoinSerialize(stream); + coinbaseBranch.bitcoinSerialize(stream); + chainMerkleBranch.bitcoinSerialize(stream); - parentBlockHeader.bitcoinSerializeToStream(stream); + parentBlockHeader.bitcoinSerializeToStream(stream); } @Override @@ -178,11 +188,11 @@ public class AuxPoW extends ChildMessage implements Serializable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AuxPoW input = (AuxPoW) o; - if (!transaction.equals(input.transaction)) return false; - if (!hashBlock.equals(input.hashBlock)) return false; - if (!coinbaseBranch.equals(input.hashBlock)) return false; - if (!blockchainBranch.equals(input.hashBlock)) return false; - if (!parentBlockHeader.equals(input.hashBlock)) return false; + if (!transaction.equals(input.transaction)) return false; + if (!hashBlock.equals(input.hashBlock)) return false; + if (!coinbaseBranch.equals(input.hashBlock)) return false; + if (!chainMerkleBranch.equals(input.hashBlock)) return false; + if (!parentBlockHeader.equals(input.hashBlock)) return false; return getHash().equals(input.getHash()); } @@ -192,7 +202,7 @@ public class AuxPoW extends ChildMessage implements Serializable { result = 31 * result + transaction.hashCode(); result = 31 * result + hashBlock.hashCode(); result = 31 * result + coinbaseBranch.hashCode(); - result = 31 * result + blockchainBranch.hashCode(); + result = 31 * result + chainMerkleBranch.hashCode(); result = 31 * result + parentBlockHeader.hashCode(); return result; } @@ -213,7 +223,7 @@ public class AuxPoW extends ChildMessage implements Serializable { * not necessarily part of the parent blockchain, they simply must be valid * blocks at the difficulty of the child blockchain. */ - public Block getParentBlockHeader() { + public AltcoinBlock getParentBlockHeader() { return parentBlockHeader; } @@ -229,8 +239,8 @@ public class AuxPoW extends ChildMessage implements Serializable { /** * Get the Merkle branch used to connect the AuXPow header with this blockchain. */ - public MerkleBranch getBlockchainBranch() { - return blockchainBranch; + public MerkleBranch getChainMerkleBranch() { + return chainMerkleBranch; } /** @@ -260,4 +270,170 @@ public class AuxPoW extends ChildMessage implements Serializable { maybeParse(); // TODO: Verify the AuxPoW data } + + /** + * Check the proof of work for this AuxPoW header meets the target + * difficulty. + * + * @param hashAuxBlock hash of the block the AuxPoW header is attached to. + * @param target the difficulty target after decoding from compact bits. + */ + protected boolean checkProofOfWork(Sha256Hash hashAuxBlock, + BigInteger target, boolean throwException) throws VerificationException { + if (0 != this.getCoinbaseBranch().getIndex()) { + if (throwException) { + // I don't like the message, but it correlates with what's in the reference client. + throw new VerificationException("AuxPow is not a generate"); + } + return false; + } + + /* if (!TestNet() + parentBlockHeader.getChainID() == ((AuxPoWNetworkParameters) params).getChainID()) { + if (throwException) { + throw new VerificationException("Aux POW parent has our chain ID"); + } + return false; + } */ + + if (this.getChainMerkleBranch().size() > 30) { + if (throwException) { + throw new VerificationException("Aux POW chain merkle branch too long"); + } + return false; + } + + // Check that the chain merkle root is in the coinbase + Sha256Hash nRootHash = getChainMerkleBranch().calculateMerkleRoot(hashAuxBlock); + final byte[] vchRootHash = nRootHash.getBytes(); + //std::reverse(vchRootHash.begin(), vchRootHash.end()); // correct endian// correct endian + + // Check that we are in the parent block merkle tree + if (!getCoinbaseBranch().calculateMerkleRoot(getCoinbase().getHash()).equals(parentBlockHeader.getMerkleRoot())) { + if (throwException) { + throw new VerificationException("Aux POW merkle root incorrect"); + } + return false; + } + + final byte[] script = this.getCoinbase().getInput(0).getScriptBytes(); + + // Check that the same work is not submitted twice to our chain. + // + 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 (pcHead >= 0) { + if (throwException) { + throw new VerificationException("Multiple merged mining headers in coinbase"); + } + return false; + } + pcHead = scriptIdx; + } + if (arrayMatch(script, scriptIdx, vchRootHash)) { + pc = scriptIdx; + } + } + + if (-1 == pcHead) { + if (throwException) { + throw new VerificationException("MergedMiningHeader missing from parent coinbase"); + } + return false; + } + + if (-1 == pc) { + if (throwException) { + throw new VerificationException("Aux POW missing chain merkle root in parent coinbase"); + } + return false; + } + + if (pcHead + MERGED_MINING_HEADER.length != pc) { + if (throwException) { + throw new VerificationException("Merged mining header is not just before chain merkle root"); + } + return false; + } + + // Ensure we are at a deterministic point in the merkle leaves by hashing + // a nonce and our chain ID and comparing to the index. + pc += vchRootHash.length; + if ((script.length - pc) < 8) { + if (throwException) { + throw new VerificationException("Aux POW missing chain merkle tree size and nonce in parent coinbase"); + } + return false; + } + + byte[] sizeBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc, pc + 4)); + int branchSize = ByteBuffer.wrap(sizeBytes).getInt(); + if (branchSize != (1 << getChainMerkleBranch().size())) { + if (throwException) { + throw new VerificationException("Aux POW merkle branch size does not match parent coinbase"); + } + return false; + } + + byte[] nonceBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc + 4, pc + 8)); + int nonce = ByteBuffer.wrap(nonceBytes).getInt(); + + // Choose a pseudo-random slot in the chain merkle tree + // but have it be fixed for a size/nonce/chain combination. + // + // This prevents the same work from being used twice for the + // same chain while reducing the chance that two chains clash + // for the same slot. + long rand = nonce; + rand = rand * 1103515245 + 12345; + rand += ((AltcoinNetworkParameters) params).getChainID(); + rand = rand * 1103515245 + 12345; + + if (getChainMerkleBranch().getIndex() != (rand % branchSize)) { + if (throwException) { + throw new VerificationException("Aux POW wrong index"); + } + return false; + } + + BigInteger h = ((AltcoinNetworkParameters) params) + .getBlockDifficultyHash(getParentBlockHeader()) + .toBigInteger(); + if (h.compareTo(target) > 0) { + // Proof of work check failed! + if (throwException) { + throw new VerificationException("Hash is higher than target: " + getParentBlockHeader().getHashAsString() + " vs " + + target.toString(16)); + } + return false; + } + + return true; + } + + public Transaction getTransaction() { + return transaction; + } + + /** + * Test whether one array is at a specific offset within the other. + * + * @param script the longer array to test for containing another array. + * @param offset the offset to start at within the larger array. + * @param subArray the shorter array to test for presence in the longer array. + * @return true if the shorter array is present at the offset, false otherwise. + */ + private boolean arrayMatch(byte[] script, int offset, byte[] subArray) { + for (int matchIdx = 0; matchIdx + offset < script.length && matchIdx < subArray.length; matchIdx++) { + if (script[offset + matchIdx] != subArray[matchIdx]) { + return false; + } + } + return true; + } } diff --git a/src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java b/src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java deleted file mode 100644 index 00f36788..00000000 --- a/src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.bitcoinj.core; - -/** - * - * @author jrn - */ -public interface AuxPoWNetworkParameters { - - public boolean isAuxPoWBlockVersion(long version); - -} diff --git a/src/main/java/org/bitcoinj/core/MerkleBranch.java b/src/main/java/org/bitcoinj/core/MerkleBranch.java index 3acd33a6..faa38844 100644 --- a/src/main/java/org/bitcoinj/core/MerkleBranch.java +++ b/src/main/java/org/bitcoinj/core/MerkleBranch.java @@ -35,6 +35,9 @@ import java.util.List; * up to its root, plus a bitset used to define how the hashes are applied. * Given the hash of the leaf, this can be used to calculate the tree * root. This is useful for proving that a leaf belongs to a given tree. + * + * TODO: Has a lot of similarity to PartialMerkleTree, should attempt to merge + * the two. */ public class MerkleBranch extends ChildMessage implements Serializable { private static final long serialVersionUID = 2; @@ -46,15 +49,15 @@ public class MerkleBranch extends ChildMessage implements Serializable { // can properly keep track of optimal encoded size private transient int optimalEncodingMessageSize; - private List branchHashes; - private long branchSideMask; + private List branchHashes; + private long index; public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) { super(params); setParent(parent); this.branchHashes = new ArrayList(); - this.branchSideMask = 0; + this.index = 0; } /** @@ -70,17 +73,13 @@ public class MerkleBranch extends ChildMessage implements Serializable { * @param params NetworkParameters object. * @param payload Bitcoin protocol formatted byte array containing message content. * @param offset The location of the first payload byte within the array. - * @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. - * @param parseRetain Whether to retain the backing byte array for quick reserialization. - * If true and the backing byte array is invalidated due to modification of a field then - * the cached bytes may be repopulated and retained if the message is serialized again in the future. - * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH + * @param serializer the serializer to use for this message. * @throws ProtocolException */ public MerkleBranch(NetworkParameters params, ChildMessage parent, byte[] payload, int offset, - boolean parseLazy, boolean parseRetain) + MessageSerializer serializer) throws ProtocolException { - super(params, payload, offset, parent, parseLazy, parseRetain, UNKNOWN_LENGTH); + super(params, payload, offset, parent, serializer, UNKNOWN_LENGTH); } public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent, @@ -89,7 +88,7 @@ public class MerkleBranch extends ChildMessage implements Serializable { setParent(parent); this.branchHashes = hashes; - this.branchSideMask = branchSideMask; + this.index = branchSideMask; } @Override @@ -118,7 +117,7 @@ public class MerkleBranch extends ChildMessage implements Serializable { branchHashes.add(readHash()); } optimalEncodingMessageSize += 32 * hashCount; - branchSideMask = readUint32(); + index = readUint32(); optimalEncodingMessageSize += 4; } @@ -128,7 +127,7 @@ public class MerkleBranch extends ChildMessage implements Serializable { for (Sha256Hash hash: branchHashes) { stream.write(Utils.reverseBytes(hash.getBytes())); } - Utils.uint32ToByteStreamLE(branchSideMask, stream); + Utils.uint32ToByteStreamLE(index, stream); } /** @@ -137,7 +136,7 @@ public class MerkleBranch extends ChildMessage implements Serializable { */ public Sha256Hash calculateMerkleRoot(final Sha256Hash leaf) { byte[] target = reverseBytes(leaf.getBytes()); - long mask = branchSideMask; + long mask = index; for (Sha256Hash hash: branchHashes) { target = (mask & 1) == 0 @@ -155,10 +154,19 @@ public class MerkleBranch extends ChildMessage implements Serializable { return Collections.unmodifiableList(this.branchHashes); } + /** + * Return the mask used to determine which side the hashes are applied on. + * Each bit represents a hash. Zero means it goes on the right, one means + * on the left. + */ + public long getIndex() { + return index; + } + /** * Get the number of hashes in this branch. */ - public int getSize() { + public int size() { return branchHashes.size(); } @@ -207,8 +215,8 @@ public class MerkleBranch extends ChildMessage implements Serializable { MerkleBranch input = (MerkleBranch) o; - if (!branchHashes.equals(input.branchHashes)) return false; - if (branchSideMask != input.branchSideMask) return false; + if (!branchHashes.equals(input.branchHashes)) return false; + if (index != input.index) return false; return true; } @@ -217,7 +225,7 @@ public class MerkleBranch extends ChildMessage implements Serializable { public int hashCode() { int result = 1; result = 31 * result + branchHashes.hashCode(); - result = 31 * result + (int) branchSideMask; + result = 31 * result + (int) index; return result; } } diff --git a/src/test/java/org/bitcoinj/core/AuxPoWTest.java b/src/test/java/org/bitcoinj/core/AuxPoWTest.java index ddedd559..3238ce99 100644 --- a/src/test/java/org/bitcoinj/core/AuxPoWTest.java +++ b/src/test/java/org/bitcoinj/core/AuxPoWTest.java @@ -1,6 +1,7 @@ package org.bitcoinj.core; -import org.bitcoinj.params.TestNet3Params; +import java.math.BigInteger; +import org.altcoinj.params.DogecoinTestNet3Params; import org.junit.Test; import static org.bitcoinj.core.Util.getBytes; @@ -12,7 +13,7 @@ import static org.junit.Assert.assertEquals; * AuxPoW header parsing/serialization and validation */ public class AuxPoWTest { - static final NetworkParameters params = TestNet3Params.get(); + static final NetworkParameters params = DogecoinTestNet3Params.get(); /** * Parse the AuxPoW header from Dogecoin block #403,931. @@ -20,13 +21,13 @@ public class AuxPoWTest { @Test public void parseAuxPoWHeader() throws Exception { byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin")); - AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, false, false); + AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer()); MerkleBranch branch = auxpow.getCoinbaseBranch(); Sha256Hash expected = new Sha256Hash("089b911f5e471c0e1800f3384281ebec5b372fbb6f358790a92747ade271ccdf"); assertEquals(expected, auxpow.getCoinbase().getHash()); - assertEquals(3, auxpow.getCoinbaseBranch().getSize()); - assertEquals(6, auxpow.getBlockchainBranch().getSize()); + assertEquals(3, auxpow.getCoinbaseBranch().size()); + assertEquals(6, auxpow.getChainMerkleBranch().size()); expected = new Sha256Hash("a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c"); assertEquals(expected, auxpow.getParentBlockHeader().getHash()); @@ -38,10 +39,21 @@ public class AuxPoWTest { @Test public void serializeAuxPoWHeader() throws Exception { byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin")); - AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, false, false); + AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer()); byte[] expected = auxpowAsBytes; byte[] actual = auxpow.bitcoinSerialize(); assertArrayEquals(expected, actual); } + + /** + * Validate the AuxPoW header from Dogecoin block #403,931. + */ + @Test + public void checkAuxPoWHeader() throws Exception { + byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin")); + AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer()); + auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"), + Utils.decodeCompactBits(0x1b06f8f0), true); + } } diff --git a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java index 9eb929f3..19f6dd0d 100644 --- a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java +++ b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java @@ -29,7 +29,8 @@ public class DogecoinBlockTest { @Test public void shouldParseBlock1() throws IOException { byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block1.bin")); - final AltcoinBlock block = new AltcoinBlock(params, payload); + AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer(); + final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload); assertEquals("82bc68038f6034c0596b6e313729793a887fded6e92a31fbdf70863f89d9bea2", block.getHashAsString()); assertEquals(1, block.getTransactions().size()); } @@ -41,7 +42,8 @@ public class DogecoinBlockTest { @Test public void shouldParseBlock250000() throws IOException { byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block250000.bin")); - final AltcoinBlock block = new AltcoinBlock(params, payload); + AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer(); + final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload); assertEquals(2469341065L, block.getNonce()); final AuxPoW auxpow = block.getAuxPoW(); assertNull(auxpow); @@ -58,7 +60,8 @@ public class DogecoinBlockTest { @Test public void shouldParseBlock371337() throws IOException { byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin")); - final AltcoinBlock block = new AltcoinBlock(params, payload); + AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer(); + final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload); assertEquals("60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053", block.getHashAsString()); assertEquals(0, block.getNonce()); final AuxPoW auxpow = block.getAuxPoW(); @@ -69,14 +72,14 @@ public class DogecoinBlockTest { assertEquals("45df41e40aba5b2a03d08bd1202a1c02ef3954d8aa22ea6c5ae62fd00f290ea9", parentBlock.getHashAsString()); assertNull(parentBlock.getTransactions()); - final MerkleBranch blockchainMerkleBranch = auxpow.getBlockchainBranch(); + final MerkleBranch blockchainMerkleBranch = auxpow.getChainMerkleBranch(); Sha256Hash[] expected = new Sha256Hash[] { new Sha256Hash("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"), new Sha256Hash("78d6abe48cee514cf3496f4042039acb7e27616dcfc5de926ff0d6c7e5987be7"), new Sha256Hash("a0469413ce64d67c43902d54ee3a380eff12ded22ca11cbd3842e15d48298103") }; - assertArrayEquals(expected, blockchainMerkleBranch.getHashes().toArray(new Sha256Hash[blockchainMerkleBranch.getSize()])); + assertArrayEquals(expected, blockchainMerkleBranch.getHashes().toArray(new Sha256Hash[blockchainMerkleBranch.size()])); final MerkleBranch coinbaseMerkleBranch = auxpow.getCoinbaseBranch(); expected = new Sha256Hash[] { @@ -84,7 +87,7 @@ public class DogecoinBlockTest { new Sha256Hash("48f9e8fef3411944e27f49ec804462c9e124dca0954c71c8560e8a9dd218a452"), new Sha256Hash("d11293660392e7c51f69477a6130237c72ecee2d0c1d3dc815841734c370331a") }; - assertArrayEquals(expected, coinbaseMerkleBranch.getHashes().toArray(new Sha256Hash[coinbaseMerkleBranch.getSize()])); + assertArrayEquals(expected, coinbaseMerkleBranch.getHashes().toArray(new Sha256Hash[coinbaseMerkleBranch.size()])); assertEquals(6, block.getTransactions().size()); } diff --git a/src/test/java/org/bitcoinj/core/MerkleBranchTest.java b/src/test/java/org/bitcoinj/core/MerkleBranchTest.java index fa1f7473..b968b550 100644 --- a/src/test/java/org/bitcoinj/core/MerkleBranchTest.java +++ b/src/test/java/org/bitcoinj/core/MerkleBranchTest.java @@ -1,6 +1,7 @@ package org.bitcoinj.core; +import static org.bitcoinj.core.AuxPoWTest.params; import org.bitcoinj.params.TestNet3Params; import org.junit.Test; @@ -28,7 +29,7 @@ public class MerkleBranchTest { new Sha256Hash("d8c6fe42ca25076159cd121a5e20c48c1bc53ab90730083e44a334566ea6bbcb") }; - assertArrayEquals(expected, branch.getHashes().toArray(new Sha256Hash[branch.getSize()])); + assertArrayEquals(expected, branch.getHashes().toArray(new Sha256Hash[branch.size()])); } /** @@ -39,7 +40,7 @@ public class MerkleBranchTest { public void serializeMerkleBranch() throws Exception { byte[] expected = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin")); MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, expected, 0, - false, false); + params.getDefaultSerializer()); byte[] actual = branch.bitcoinSerialize(); assertArrayEquals(expected, actual);