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 }