Coverage Summary for Class: ProofOfWorkRule (co.rsk.validators)

Class Class, % Method, % Line, %
ProofOfWorkRule 0% (0/1) 0% (0/8) 0% (0/114)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2017 RSK Labs Ltd. 4  * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>) 5  * 6  * This program is free software: you can redistribute it and/or modify 7  * it under the terms of the GNU Lesser General Public License as published by 8  * the Free Software Foundation, either version 3 of the License, or 9  * (at your option) any later version. 10  * 11  * This program is distributed in the hope that it will be useful, 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14  * GNU Lesser General Public License for more details. 15  * 16  * You should have received a copy of the GNU Lesser General Public License 17  * along with this program. If not, see <http://www.gnu.org/licenses/>. 18  */ 19  20 package co.rsk.validators; 21  22 import co.rsk.bitcoinj.core.BtcBlock; 23 import co.rsk.bitcoinj.core.Sha256Hash; 24 import co.rsk.config.BridgeConstants; 25 import co.rsk.config.RskMiningConstants; 26 import co.rsk.config.RskSystemProperties; 27 import co.rsk.util.DifficultyUtils; 28 import co.rsk.util.ListArrayUtil; 29 import com.google.common.annotations.VisibleForTesting; 30 import org.bouncycastle.crypto.digests.SHA256Digest; 31 import org.bouncycastle.util.Pack; 32 import org.ethereum.config.Constants; 33 import org.ethereum.config.blockchain.upgrades.ActivationConfig; 34 import org.ethereum.config.blockchain.upgrades.ConsensusRule; 35 import org.ethereum.core.Block; 36 import org.ethereum.core.BlockHeader; 37 import org.ethereum.crypto.ECKey; 38 import org.ethereum.crypto.signature.ECDSASignature; 39 import org.ethereum.crypto.signature.Secp256k1; 40 import org.ethereum.util.RLP; 41 import org.ethereum.util.RLPElement; 42 import org.ethereum.util.RLPList; 43 import org.slf4j.Logger; 44 import org.slf4j.LoggerFactory; 45  46 import java.math.BigInteger; 47 import java.util.Arrays; 48 import java.util.List; 49  50 /** 51  * Checks proof value against its boundary for the block header. 52  */ 53 public class ProofOfWorkRule implements BlockHeaderValidationRule, BlockValidationRule { 54  55  private static final Logger logger = LoggerFactory.getLogger("blockvalidator"); 56  private static final BigInteger SECP256K1N_HALF = Constants.getSECP256K1N().divide(BigInteger.valueOf(2)); 57  58  private final BridgeConstants bridgeConstants; 59  private final Constants constants; 60  private final ActivationConfig activationConfig; 61  private boolean fallbackMiningEnabled = true; 62  63  public ProofOfWorkRule(RskSystemProperties config) { 64  this.activationConfig = config.getActivationConfig(); 65  this.constants = config.getNetworkConstants(); 66  this.bridgeConstants = constants.getBridgeConstants(); 67  } 68  69  @VisibleForTesting 70  public ProofOfWorkRule setFallbackMiningEnabled(boolean e) { 71  fallbackMiningEnabled = e; 72  return this; 73  } 74  75  @Override 76  public boolean isValid(Block block) { 77  return isValid(block.getHeader()); 78  } 79  80  private boolean isFallbackMiningPossible(BlockHeader header) { 81  if (activationConfig.isActive(ConsensusRule.RSKIP98, header.getNumber())) { 82  return false; 83  } 84  85  if (header.getDifficulty().compareTo(constants.getFallbackMiningDifficulty()) > 0) { 86  return false; 87  } 88  89  // If more than 10 minutes have elapsed, and difficulty is lower than 4 peta/s (config) 90  // then private mining is still possible, but only after 10 minutes of inactivity or 91  // previous block was privately mined. 92  // This difficulty reset will be computed in DifficultyRule 93  return true; 94  } 95  96  private boolean isFallbackMiningPossibleAndBlockSigned(BlockHeader header) { 97  98  if (header.getBitcoinMergedMiningCoinbaseTransaction() != null) { 99  return false; 100  } 101  102  byte[] merkleProof = header.getBitcoinMergedMiningMerkleProof(); 103  if (merkleProof != null && merkleProof.length > 0) { 104  return false; 105  } 106  107  if (!fallbackMiningEnabled) { 108  return false; 109  } 110  111  return isFallbackMiningPossible(header); 112  113  } 114  115  @Override 116  public boolean isValid(BlockHeader header) { 117  // TODO: refactor this an move it to another class. Change the Global ProofOfWorkRule to AuthenticationRule. 118  // TODO: Make ProofOfWorkRule one of the classes that inherits from AuthenticationRule. 119  if (isFallbackMiningPossibleAndBlockSigned(header)) { 120  boolean isValidFallbackSignature = validFallbackBlockSignature(constants, header, header.getBitcoinMergedMiningHeader()); 121  if (!isValidFallbackSignature) { 122  logger.warn("Fallback signature failed. Header {}", header.getPrintableHash()); 123  } 124  return isValidFallbackSignature; 125  } 126  127  co.rsk.bitcoinj.core.NetworkParameters bitcoinNetworkParameters = bridgeConstants.getBtcParams(); 128  MerkleProofValidator mpValidator; 129  try { 130  if (activationConfig.isActive(ConsensusRule.RSKIP92, header.getNumber())) { 131  boolean isRskip180Enabled = activationConfig.isActive(ConsensusRule.RSKIP180, header.getNumber()); 132  mpValidator = new Rskip92MerkleProofValidator(header.getBitcoinMergedMiningMerkleProof(), isRskip180Enabled); 133  } else { 134  mpValidator = new GenesisMerkleProofValidator(bitcoinNetworkParameters, header.getBitcoinMergedMiningMerkleProof()); 135  } 136  } catch (RuntimeException ex) { 137  logger.warn("Merkle proof can't be validated. Header {}", header.getPrintableHash(), ex); 138  return false; 139  } 140  141  byte[] bitcoinMergedMiningCoinbaseTransactionCompressed = header.getBitcoinMergedMiningCoinbaseTransaction(); 142  143  if (bitcoinMergedMiningCoinbaseTransactionCompressed == null) { 144  logger.warn("Compressed coinbase transaction does not exist. Header {}", header.getPrintableHash()); 145  return false; 146  } 147  148  if (header.getBitcoinMergedMiningHeader() == null) { 149  logger.warn("Bitcoin merged mining header does not exist. Header {}", header.getPrintableHash()); 150  return false; 151  } 152  153  BtcBlock bitcoinMergedMiningBlock = bitcoinNetworkParameters.getDefaultSerializer().makeBlock(header.getBitcoinMergedMiningHeader()); 154  155  BigInteger target = DifficultyUtils.difficultyToTarget(header.getDifficulty()); 156  157  BigInteger bitcoinMergedMiningBlockHashBI = bitcoinMergedMiningBlock.getHash().toBigInteger(); 158  159  if (bitcoinMergedMiningBlockHashBI.compareTo(target) > 0) { 160  logger.warn("Hash {} is higher than target {}", bitcoinMergedMiningBlockHashBI.toString(16), target.toString(16)); 161  return false; 162  } 163  164  byte[] bitcoinMergedMiningCoinbaseTransactionMidstate = new byte[RskMiningConstants.MIDSTATE_SIZE]; 165  System.arraycopy(bitcoinMergedMiningCoinbaseTransactionCompressed, 0, bitcoinMergedMiningCoinbaseTransactionMidstate, 8, RskMiningConstants.MIDSTATE_SIZE_TRIMMED); 166  167  byte[] bitcoinMergedMiningCoinbaseTransactionTail = new byte[bitcoinMergedMiningCoinbaseTransactionCompressed.length - RskMiningConstants.MIDSTATE_SIZE_TRIMMED]; 168  System.arraycopy(bitcoinMergedMiningCoinbaseTransactionCompressed, RskMiningConstants.MIDSTATE_SIZE_TRIMMED, 169  bitcoinMergedMiningCoinbaseTransactionTail, 0, bitcoinMergedMiningCoinbaseTransactionTail.length); 170  171  byte[] expectedCoinbaseMessageBytes = org.bouncycastle.util.Arrays.concatenate(RskMiningConstants.RSK_TAG, header.getHashForMergedMining()); 172  173  int rskTagPosition = ListArrayUtil.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTail, expectedCoinbaseMessageBytes); 174  if (rskTagPosition == -1) { 175  logger.warn("bitcoin coinbase transaction tail message does not contain expected" + 176  " RSKBLOCK:RskBlockHeaderHash. Expected: {} . Actual: {} .", 177  Arrays.toString(expectedCoinbaseMessageBytes), 178  Arrays.toString(bitcoinMergedMiningCoinbaseTransactionTail)); 179  return false; 180  } 181  182  /* 183  * We check that the there is no other block before the rsk tag, to avoid a possible malleability attack: 184  * If we have a mid state with 10 blocks, and the rsk tag, we can also have 185  * another mid state with 9 blocks, 64bytes + the rsk tag, giving us two blocks with different hashes but the same spv proof. 186  * */ 187  if (rskTagPosition >= 64) { 188  logger.warn("bitcoin coinbase transaction tag position is bigger than expected 64. Actual: {}.", Integer.toString(rskTagPosition)); 189  return false; 190  } 191  192  int lastTag = ListArrayUtil.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTail, RskMiningConstants.RSK_TAG); 193  if (rskTagPosition !=lastTag) { 194  logger.warn("The valid RSK tag is not the last RSK tag. Tail: {}.", Arrays.toString(bitcoinMergedMiningCoinbaseTransactionTail)); 195  return false; 196  } 197  198  int remainingByteCount = bitcoinMergedMiningCoinbaseTransactionTail.length - 199  rskTagPosition - 200  RskMiningConstants.RSK_TAG.length - 201  RskMiningConstants.BLOCK_HEADER_HASH_SIZE; 202  203  if (remainingByteCount > RskMiningConstants.MAX_BYTES_AFTER_MERGED_MINING_HASH) { 204  logger.warn("More than 128 bytes after RSK tag"); 205  return false; 206  } 207  208  // TODO test 209  long byteCount = Pack.bigEndianToLong(bitcoinMergedMiningCoinbaseTransactionMidstate, 8); 210  long coinbaseLength = bitcoinMergedMiningCoinbaseTransactionTail.length + byteCount; 211  if (coinbaseLength <= 64) { 212  logger.warn("Coinbase transaction must always be greater than 64-bytes long. But it was: {}", coinbaseLength); 213  return false; 214  } 215  216  SHA256Digest digest = new SHA256Digest(bitcoinMergedMiningCoinbaseTransactionMidstate); 217  digest.update(bitcoinMergedMiningCoinbaseTransactionTail,0,bitcoinMergedMiningCoinbaseTransactionTail.length); 218  byte[] bitcoinMergedMiningCoinbaseTransactionOneRoundOfHash = new byte[32]; 219  digest.doFinal(bitcoinMergedMiningCoinbaseTransactionOneRoundOfHash, 0); 220  Sha256Hash bitcoinMergedMiningCoinbaseTransactionHash = Sha256Hash.wrapReversed(Sha256Hash.hash(bitcoinMergedMiningCoinbaseTransactionOneRoundOfHash)); 221  222  if (!mpValidator.isValid(bitcoinMergedMiningBlock.getMerkleRoot(), bitcoinMergedMiningCoinbaseTransactionHash)) { 223  logger.warn("bitcoin merkle branch doesn't match coinbase and state root"); 224  return false; 225  } 226  227  return true; 228  } 229  230  private static boolean validFallbackBlockSignature(Constants constants, BlockHeader header, byte[] signatureBytesRLP) { 231  232  byte[] fallbackMiningPubKeyBytes; 233  boolean isEvenBlockNumber = header.getNumber() % 2 == 0; 234  if (isEvenBlockNumber) { 235  fallbackMiningPubKeyBytes = constants.getFallbackMiningPubKey0(); 236  } else { 237  fallbackMiningPubKeyBytes = constants.getFallbackMiningPubKey1(); 238  } 239  240  ECKey fallbackMiningPubKey = ECKey.fromPublicOnly(fallbackMiningPubKeyBytes); 241  242  List<RLPElement> signatureRlpElements = RLP.decode2(signatureBytesRLP); 243  if (signatureRlpElements.size() != 1) { 244  return false; 245  } 246  247  RLPList signatureRLP = (RLPList) signatureRlpElements.get(0); 248  249  if (signatureRLP.size() != 3) { 250  return false; 251  } 252  253  byte[] v = signatureRLP.get(0).getRLPData(); 254  byte[] r = signatureRLP.get(1).getRLPData(); 255  byte[] s = signatureRLP.get(2).getRLPData(); 256  257  if (v == null || v.length != 1) { 258  return false; 259  } 260  261  ECDSASignature signature = ECDSASignature.fromComponents(r, s, v[0]); 262  263  if (!Arrays.equals(r, signature.getR().toByteArray())) { 264  return false; 265  } 266  267  if (!Arrays.equals(s, signature.getS().toByteArray())) { 268  return false; 269  } 270  271  if (signature.getV() > 31 || signature.getV() < 27) { 272  return false; 273  } 274  275  if (signature.getS().compareTo(SECP256K1N_HALF) >= 0) { 276  return false; 277  } 278  279  ECKey pub = Secp256k1.getInstance().recoverFromSignature(signature.getV() - 27, signature, header.getHashForMergedMining(), false); 280  281  return pub.getPubKeyPoint().equals(fallbackMiningPubKey.getPubKeyPoint()); 282  } 283 }