Java tutorial
/* * This file is part of RskJ * Copyright (C) 2017 RSK Labs Ltd. * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package co.rsk.validators; import co.rsk.bitcoinj.core.BtcBlock; import co.rsk.bitcoinj.core.Sha256Hash; import co.rsk.config.BridgeConstants; import co.rsk.config.RskMiningConstants; import co.rsk.config.RskSystemProperties; import co.rsk.util.DifficultyUtils; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.util.Pack; import org.ethereum.config.BlockchainNetConfig; import org.ethereum.config.Constants; import org.ethereum.core.Block; import org.ethereum.core.BlockHeader; import org.ethereum.crypto.ECKey; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Checks proof value against its boundary for the block header. */ @Component public class ProofOfWorkRule implements BlockHeaderValidationRule, BlockValidationRule { private static final Logger logger = LoggerFactory.getLogger("blockvalidator"); private static final BigInteger SECP256K1N_HALF = Constants.getSECP256K1N().divide(BigInteger.valueOf(2)); private final RskSystemProperties config; private final BlockchainNetConfig blockchainConfig; private final BridgeConstants bridgeConstants; private final Constants constants; private boolean fallbackMiningEnabled = true; @Autowired public ProofOfWorkRule(RskSystemProperties config) { this.config = config; this.blockchainConfig = config.getBlockchainConfig(); this.bridgeConstants = blockchainConfig.getCommonConstants().getBridgeConstants(); this.constants = blockchainConfig.getCommonConstants(); } @VisibleForTesting public ProofOfWorkRule setFallbackMiningEnabled(boolean e) { fallbackMiningEnabled = e; return this; } @Override public boolean isValid(Block block) { return isValid(block.getHeader()); } private boolean isFallbackMiningPossible(BlockHeader header) { if (config.getBlockchainConfig().getConfigForBlock(header.getNumber()).isRskip98()) { return false; } Constants constants = config.getBlockchainConfig().getCommonConstants(); if (header.getDifficulty().compareTo(constants.getFallbackMiningDifficulty()) > 0) { return false; } // If more than 10 minutes have elapsed, and difficulty is lower than 4 peta/s (config) // then private mining is still possible, but only after 10 minutes of inactivity or // previous block was privately mined. // This difficulty reset will be computed in DifficultyRule return true; } private boolean isFallbackMiningPossibleAndBlockSigned(BlockHeader header) { if (header.getBitcoinMergedMiningCoinbaseTransaction() != null) { return false; } byte[] merkleProof = header.getBitcoinMergedMiningMerkleProof(); if (merkleProof != null && merkleProof.length > 0) { return false; } if (!fallbackMiningEnabled) { return false; } return isFallbackMiningPossible(header); } @Override public boolean isValid(BlockHeader header) { // TODO: refactor this an move it to another class. Change the Global ProofOfWorkRule to AuthenticationRule. // TODO: Make ProofOfWorkRule one of the classes that inherits from AuthenticationRule. if (isFallbackMiningPossibleAndBlockSigned(header)) { boolean isValidFallbackSignature = validFallbackBlockSignature(constants, header, header.getBitcoinMergedMiningHeader()); if (!isValidFallbackSignature) { logger.warn("Fallback signature failed. Header {}", header.getShortHash()); } return isValidFallbackSignature; } co.rsk.bitcoinj.core.NetworkParameters bitcoinNetworkParameters = bridgeConstants.getBtcParams(); MerkleProofValidator mpValidator; try { if (blockchainConfig.getConfigForBlock(header.getNumber()).isRskip92()) { mpValidator = new Rskip92MerkleProofValidator(header.getBitcoinMergedMiningMerkleProof()); } else { mpValidator = new GenesisMerkleProofValidator(bitcoinNetworkParameters, header.getBitcoinMergedMiningMerkleProof()); } } catch (RuntimeException ex) { logger.warn("Merkle proof can't be validated. Header {}", header.getShortHash(), ex); return false; } byte[] bitcoinMergedMiningCoinbaseTransactionCompressed = header .getBitcoinMergedMiningCoinbaseTransaction(); if (bitcoinMergedMiningCoinbaseTransactionCompressed == null) { logger.warn("Compressed coinbase transaction does not exist. Header {}", header.getShortHash()); return false; } if (header.getBitcoinMergedMiningHeader() == null) { logger.warn("Bitcoin merged mining header does not exist. Header {}", header.getShortHash()); return false; } BtcBlock bitcoinMergedMiningBlock = bitcoinNetworkParameters.getDefaultSerializer() .makeBlock(header.getBitcoinMergedMiningHeader()); BigInteger target = DifficultyUtils.difficultyToTarget(header.getDifficulty()); BigInteger bitcoinMergedMiningBlockHashBI = bitcoinMergedMiningBlock.getHash().toBigInteger(); if (bitcoinMergedMiningBlockHashBI.compareTo(target) > 0) { logger.warn("Hash {} is higher than target {}", bitcoinMergedMiningBlockHashBI.toString(16), target.toString(16)); return false; } byte[] bitcoinMergedMiningCoinbaseTransactionMidstate = new byte[RskMiningConstants.MIDSTATE_SIZE]; System.arraycopy(bitcoinMergedMiningCoinbaseTransactionCompressed, 0, bitcoinMergedMiningCoinbaseTransactionMidstate, 8, RskMiningConstants.MIDSTATE_SIZE_TRIMMED); byte[] bitcoinMergedMiningCoinbaseTransactionTail = new byte[bitcoinMergedMiningCoinbaseTransactionCompressed.length - RskMiningConstants.MIDSTATE_SIZE_TRIMMED]; System.arraycopy(bitcoinMergedMiningCoinbaseTransactionCompressed, RskMiningConstants.MIDSTATE_SIZE_TRIMMED, bitcoinMergedMiningCoinbaseTransactionTail, 0, bitcoinMergedMiningCoinbaseTransactionTail.length); byte[] expectedCoinbaseMessageBytes = org.bouncycastle.util.Arrays.concatenate(RskMiningConstants.RSK_TAG, header.getHashForMergedMining()); List<Byte> bitcoinMergedMiningCoinbaseTransactionTailAsList = Arrays .asList(ArrayUtils.toObject(bitcoinMergedMiningCoinbaseTransactionTail)); List<Byte> expectedCoinbaseMessageBytesAsList = Arrays .asList(ArrayUtils.toObject(expectedCoinbaseMessageBytes)); int rskTagPosition = Collections.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTailAsList, expectedCoinbaseMessageBytesAsList); if (rskTagPosition == -1) { logger.warn( "bitcoin coinbase transaction tail message does not contain expected RSKBLOCK:RskBlockHeaderHash. Expected: {} . Actual: {} .", Arrays.toString(expectedCoinbaseMessageBytes), Arrays.toString(bitcoinMergedMiningCoinbaseTransactionTail)); return false; } /* * We check that the there is no other block before the rsk tag, to avoid a possible malleability attack: * If we have a mid state with 10 blocks, and the rsk tag, we can also have * another mid state with 9 blocks, 64bytes + the rsk tag, giving us two blocks with different hashes but the same spv proof. * */ if (rskTagPosition >= 64) { logger.warn("bitcoin coinbase transaction tag position is bigger than expected 64. Actual: {}.", Integer.toString(rskTagPosition)); return false; } List<Byte> rskTagAsList = Arrays.asList(ArrayUtils.toObject(RskMiningConstants.RSK_TAG)); int lastTag = Collections.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTailAsList, rskTagAsList); if (rskTagPosition != lastTag) { logger.warn("The valid RSK tag is not the last RSK tag. Tail: {}.", Arrays.toString(bitcoinMergedMiningCoinbaseTransactionTail)); return false; } int remainingByteCount = bitcoinMergedMiningCoinbaseTransactionTail.length - rskTagPosition - RskMiningConstants.RSK_TAG.length - RskMiningConstants.BLOCK_HEADER_HASH_SIZE; if (remainingByteCount > RskMiningConstants.MAX_BYTES_AFTER_MERGED_MINING_HASH) { logger.warn("More than 128 bytes after RSK tag"); return false; } // TODO test long byteCount = Pack.bigEndianToLong(bitcoinMergedMiningCoinbaseTransactionMidstate, 8); long coinbaseLength = bitcoinMergedMiningCoinbaseTransactionTail.length + byteCount; if (coinbaseLength <= 64) { logger.warn("Coinbase transaction must always be greater than 64-bytes long. But it was: {}", coinbaseLength); return false; } SHA256Digest digest = new SHA256Digest(bitcoinMergedMiningCoinbaseTransactionMidstate); digest.update(bitcoinMergedMiningCoinbaseTransactionTail, 0, bitcoinMergedMiningCoinbaseTransactionTail.length); byte[] bitcoinMergedMiningCoinbaseTransactionOneRoundOfHash = new byte[32]; digest.doFinal(bitcoinMergedMiningCoinbaseTransactionOneRoundOfHash, 0); Sha256Hash bitcoinMergedMiningCoinbaseTransactionHash = Sha256Hash .wrapReversed(Sha256Hash.hash(bitcoinMergedMiningCoinbaseTransactionOneRoundOfHash)); if (!mpValidator.isValid(bitcoinMergedMiningBlock.getMerkleRoot(), bitcoinMergedMiningCoinbaseTransactionHash)) { logger.warn("bitcoin merkle branch doesn't match coinbase and state root"); return false; } return true; } private static boolean validFallbackBlockSignature(Constants constants, BlockHeader header, byte[] signatureBytesRLP) { byte[] fallbackMiningPubKeyBytes; boolean isEvenBlockNumber = header.getNumber() % 2 == 0; if (isEvenBlockNumber) { fallbackMiningPubKeyBytes = constants.getFallbackMiningPubKey0(); } else { fallbackMiningPubKeyBytes = constants.getFallbackMiningPubKey1(); } ECKey fallbackMiningPubKey = ECKey.fromPublicOnly(fallbackMiningPubKeyBytes); List<RLPElement> signatureRlpElements = RLP.decode2(signatureBytesRLP); if (signatureRlpElements.size() != 1) { return false; } List<RLPElement> signatureRLP = (RLPList) signatureRlpElements.get(0); if (signatureRLP.size() != 3) { return false; } byte[] v = signatureRLP.get(0).getRLPData(); byte[] r = signatureRLP.get(1).getRLPData(); byte[] s = signatureRLP.get(2).getRLPData(); if (v == null || v.length != 1) { return false; } ECKey.ECDSASignature signature = ECKey.ECDSASignature.fromComponents(r, s, v[0]); if (!Arrays.equals(r, signature.r.toByteArray())) { return false; } if (!Arrays.equals(s, signature.s.toByteArray())) { return false; } if (signature.v > 31 || signature.v < 27) { return false; } if (signature.s.compareTo(SECP256K1N_HALF) >= 0) { return false; } ECKey pub = ECKey.recoverFromSignature(signature.v - 27, signature, header.getHashForMergedMining(), false); return pub.getPubKeyPoint().equals(fallbackMiningPubKey.getPubKeyPoint()); } }