Coverage Summary for Class: BridgeUtils (co.rsk.peg)
Class |
Class, %
|
Method, %
|
Line, %
|
BridgeUtils |
100%
(1/1)
|
5.7%
(2/35)
|
2.9%
(6/207)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package co.rsk.peg;
20
21 import co.rsk.bitcoinj.core.*;
22 import co.rsk.bitcoinj.crypto.TransactionSignature;
23 import co.rsk.bitcoinj.script.RedeemScriptParser;
24 import co.rsk.bitcoinj.script.RedeemScriptParser.MultiSigType;
25 import co.rsk.bitcoinj.script.RedeemScriptParserFactory;
26 import co.rsk.bitcoinj.script.Script;
27 import co.rsk.bitcoinj.script.ScriptBuilder;
28 import co.rsk.bitcoinj.script.ScriptChunk;
29 import co.rsk.bitcoinj.wallet.Wallet;
30 import co.rsk.config.BridgeConstants;
31 import co.rsk.core.RskAddress;
32 import co.rsk.peg.bitcoin.RskAllowUnconfirmedCoinSelector;
33 import co.rsk.peg.btcLockSender.BtcLockSender.TxSenderAddressType;
34 import co.rsk.peg.utils.BtcTransactionFormatUtils;
35 import javax.annotation.Nonnull;
36 import org.ethereum.config.Constants;
37 import org.ethereum.config.blockchain.upgrades.ActivationConfig;
38 import org.ethereum.config.blockchain.upgrades.ConsensusRule;
39 import org.ethereum.core.Transaction;
40 import org.ethereum.vm.PrecompiledContracts;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import java.math.BigInteger;
45 import java.util.*;
46 import java.util.stream.Stream;
47
48 /**
49 * @author Oscar Guindzberg
50 */
51 public class BridgeUtils {
52
53 private static final Logger logger = LoggerFactory.getLogger("BridgeUtils");
54
55 public static Wallet getFederationNoSpendWallet(
56 Context btcContext,
57 Federation federation,
58 boolean isFastBridgeCompatible,
59 BridgeStorageProvider storageProvider
60 ) {
61 return getFederationsNoSpendWallet(
62 btcContext,
63 Collections.singletonList(federation),
64 isFastBridgeCompatible,
65 storageProvider
66 );
67 }
68
69 public static Wallet getFederationsNoSpendWallet(
70 Context btcContext,
71 List<Federation> federations,
72 boolean isFastBridgeCompatible,
73 BridgeStorageProvider storageProvider
74 ) {
75 Wallet wallet;
76 if (isFastBridgeCompatible) {
77 wallet = new FastBridgeCompatibleBtcWalletWithStorage(btcContext, federations, storageProvider);
78 } else {
79 wallet = new BridgeBtcWallet(btcContext, federations);
80 }
81
82 federations.forEach(federation ->
83 wallet.addWatchedAddress(
84 federation.getAddress(),
85 federation.getCreationTime().toEpochMilli()
86 )
87 );
88
89 return wallet;
90 }
91
92 public static Wallet getFederationSpendWallet(
93 Context btcContext,
94 Federation federation,
95 List<UTXO> utxos,
96 boolean isFastBridgeCompatible,
97 BridgeStorageProvider storageProvider
98 ) {
99 return getFederationsSpendWallet(
100 btcContext,
101 Collections.singletonList(federation),
102 utxos,
103 isFastBridgeCompatible,
104 storageProvider
105 );
106 }
107
108 public static Wallet getFederationsSpendWallet(
109 Context btcContext,
110 List<Federation> federations,
111 List<UTXO> utxos,
112 boolean isFastBridgeCompatible,
113 BridgeStorageProvider storageProvider
114 ) {
115 Wallet wallet;
116 if (isFastBridgeCompatible) {
117 wallet = new FastBridgeCompatibleBtcWalletWithStorage(btcContext, federations, storageProvider);
118 } else {
119 wallet = new BridgeBtcWallet(btcContext, federations);
120 }
121
122 RskUTXOProvider utxoProvider = new RskUTXOProvider(btcContext.getParams(), utxos);
123 wallet.setUTXOProvider(utxoProvider);
124
125 federations.forEach(federation ->
126 wallet.addWatchedAddress(
127 federation.getAddress(),
128 federation.getCreationTime().toEpochMilli()
129 )
130 );
131
132 wallet.setCoinSelector(new RskAllowUnconfirmedCoinSelector());
133 return wallet;
134 }
135
136 public static boolean scriptCorrectlySpendsTx(BtcTransaction tx, int index, Script script) {
137 try {
138 TransactionInput txInput = tx.getInput(index);
139 txInput.getScriptSig().correctlySpends(tx, index, script, Script.ALL_VERIFY_FLAGS);
140 return true;
141 } catch (ScriptException se) {
142 return false;
143 }
144 }
145
146 /**
147 * It checks if the tx doesn't spend any of the federations' funds and if it sends more than
148 * the minimum ({@see BridgeConstants::getMinimumLockTxValue}) to any of the federations
149 * @param tx the BTC transaction to check
150 * @param activeFederations the active federations
151 * @param retiredFederationP2SHScript the retired federation P2SHScript. Could be {@code null}.
152 * @param btcContext the BTC Context
153 * @param bridgeConstants the Bridge constants
154 * @param activations the network HF activations configuration
155 * @return true if this is a valid peg-in transaction
156 */
157 public static boolean isValidPegInTx(
158 BtcTransaction tx,
159 List<Federation> activeFederations,
160 Script retiredFederationP2SHScript,
161 Context btcContext,
162 BridgeConstants bridgeConstants,
163 ActivationConfig.ForBlock activations) {
164
165 // First, check tx is not a typical release tx (tx spending from the any of the federation addresses and
166 // optionally sending some change to any of the federation addresses)
167 for (int i = 0; i < tx.getInputs().size(); i++) {
168 final int index = i;
169 if (activeFederations.stream().anyMatch(federation -> scriptCorrectlySpendsTx(tx, index, federation.getP2SHScript()))) {
170 return false;
171 }
172
173 if (retiredFederationP2SHScript != null && scriptCorrectlySpendsTx(tx, index, retiredFederationP2SHScript)) {
174 return false;
175 }
176
177 // Check if the registered utxo is not change from an utxo spent from either a fast bridge federation,
178 // erp federation, or even a retired fast bridge or erp federation
179 if (activations.isActive(ConsensusRule.RSKIP201)) {
180 RedeemScriptParser redeemScriptParser = RedeemScriptParserFactory.get(tx.getInput(index).getScriptSig().getChunks());
181 try {
182 Script inputStandardRedeemScript = redeemScriptParser.extractStandardRedeemScript();
183 if (activeFederations.stream().anyMatch(federation -> federation.getStandardRedeemScript().equals(inputStandardRedeemScript))) {
184 return false;
185 }
186
187 Script outputScript = ScriptBuilder.createP2SHOutputScript(inputStandardRedeemScript);
188 if (outputScript.equals(retiredFederationP2SHScript)) {
189 return false;
190 }
191 } catch (ScriptException e) {
192 // There is no redeem script, could be a peg-in from a P2PKH address
193 }
194 }
195 }
196
197 Wallet federationsWallet = BridgeUtils.getFederationsNoSpendWallet(
198 btcContext,
199 activeFederations,
200 false,
201 null
202 );
203 Coin valueSentToMe = tx.getValueSentToMe(federationsWallet);
204 Coin minimumPegInTxValue = activations.isActive(ConsensusRule.RSKIP219) ?
205 bridgeConstants.getMinimumPeginTxValueInSatoshis() :
206 bridgeConstants.getLegacyMinimumPeginTxValueInSatoshis();
207
208 if (valueSentToMe.isLessThan(minimumPegInTxValue)) {
209 logger.warn("[btctx:{}] Someone sent to the federation less than {} satoshis", tx.getHash(), minimumPegInTxValue);
210 }
211
212 return valueSentToMe.isPositive() && !valueSentToMe.isLessThan(minimumPegInTxValue);
213 }
214
215 /**
216 * It checks if the tx doesn't spend any of the federations' funds and if it sends more than
217 * the minimum ({@see BridgeConstants::getMinimumLockTxValue}) to any of the federations
218 * @param tx the BTC transaction to check
219 * @param federation the active federation
220 * @param btcContext the BTC Context
221 * @param bridgeConstants the Bridge constants
222 * @param activations the network HF activations configuration
223 * @return true if this is a valid peg-in transaction
224 */
225 public static boolean isValidPegInTx(
226 BtcTransaction tx,
227 Federation federation,
228 Context btcContext,
229 BridgeConstants bridgeConstants,
230 ActivationConfig.ForBlock activations) {
231
232 return isValidPegInTx(
233 tx,
234 Collections.singletonList(federation),
235 null,
236 btcContext,
237 bridgeConstants,
238 activations
239 );
240 }
241
242 /**
243 * It checks if the tx can be processed, if it is sent from a P2PKH address or RSKIP 143 is active
244 * and the sender could be obtained
245 * @param txSenderAddressType sender of the transaction address type
246 * @param activations to identify if certain hardfork is active or not.
247 * @return true if this tx can be locked
248 */
249 public static boolean txIsProcessableInLegacyVersion(TxSenderAddressType txSenderAddressType, ActivationConfig.ForBlock activations) {
250 //After RSKIP 143 activation, check if the tx sender could be obtained to process the tx
251 return txSenderAddressType == TxSenderAddressType.P2PKH ||
252 (activations.isActive(ConsensusRule.RSKIP143) && txSenderAddressType != TxSenderAddressType.UNKNOWN);
253 }
254
255 private static boolean isPegOutTx(BtcTransaction tx, Federation federation, ActivationConfig.ForBlock activations) {
256 return isPegOutTx(tx, Collections.singletonList(federation), activations);
257 }
258
259 public static boolean isPegOutTx(BtcTransaction tx, List<Federation> federations, ActivationConfig.ForBlock activations) {
260 return isPegOutTx(tx, activations, federations.stream().filter(Objects::nonNull).map(Federation::getP2SHScript).toArray(Script[]::new));
261 }
262
263 public static boolean isPegOutTx(BtcTransaction tx, ActivationConfig.ForBlock activations, Script... p2shScript) {
264 int inputsSize = tx.getInputs().size();
265 for (int i = 0; i < inputsSize; i++) {
266 TransactionInput txInput = tx.getInput(i);
267 Optional<Script> redeemScriptOptional = extractRedeemScriptFromInput(tx.getInput(i));
268 if (!redeemScriptOptional.isPresent()) {
269 continue;
270 }
271
272 Script redeemScript = redeemScriptOptional.get();
273 if (activations.isActive(ConsensusRule.RSKIP201)) {
274 // Extract standard redeem script since the registered utxo could be from a fast bridge or erp federation
275 RedeemScriptParser redeemScriptParser = RedeemScriptParserFactory.get(txInput.getScriptSig().getChunks());
276 try {
277 redeemScript = redeemScriptParser.extractStandardRedeemScript();
278 } catch (ScriptException e) {
279 // There is no redeem script
280 continue;
281 }
282 }
283
284 Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript);
285 if (Stream.of(p2shScript).anyMatch(federationPayScript -> federationPayScript.equals(outputScript))) {
286 return true;
287 }
288 }
289
290 return false;
291 }
292
293 public static boolean isMigrationTx(
294 BtcTransaction btcTx,
295 Federation activeFederation,
296 Federation retiringFederation,
297 Script retiredFederationP2SHScript,
298 Context btcContext,
299 BridgeConstants bridgeConstants,
300 ActivationConfig.ForBlock activations) {
301
302 if (retiredFederationP2SHScript == null && retiringFederation == null) {
303 return false;
304 }
305 boolean moveFromRetired = retiredFederationP2SHScript != null && isPegOutTx(btcTx, activations, retiredFederationP2SHScript);
306 boolean moveFromRetiring = retiringFederation != null && isPegOutTx(btcTx, retiringFederation, activations);
307 boolean moveToActive = isValidPegInTx(btcTx, activeFederation, btcContext, bridgeConstants, activations);
308
309 return (moveFromRetired || moveFromRetiring) && moveToActive;
310 }
311
312 /**
313 * Return the amount of missing signatures for a tx.
314 * @param btcContext Btc context
315 * @param btcTx The btc tx to check
316 * @return 0 if was signed by the required number of federators, amount of missing signatures otherwise
317 */
318 public static int countMissingSignatures(Context btcContext, BtcTransaction btcTx) {
319 // Check missing signatures for only one input as it is not
320 // possible for a federator to leave unsigned inputs in a tx
321 Context.propagate(btcContext);
322 int unsigned = 0;
323
324 TransactionInput input = btcTx.getInput(0);
325 Script scriptSig = input.getScriptSig();
326 List<ScriptChunk> chunks = scriptSig.getChunks();
327 Script redeemScript = new Script(chunks.get(chunks.size() - 1).data);
328 RedeemScriptParser parser = RedeemScriptParserFactory.get(redeemScript.getChunks());
329 MultiSigType multiSigType;
330
331 int lastChunk;
332
333 multiSigType = parser.getMultiSigType();
334
335 if (multiSigType == MultiSigType.STANDARD_MULTISIG ||
336 multiSigType == MultiSigType.FAST_BRIDGE_MULTISIG
337 ) {
338 lastChunk = chunks.size() - 1;
339 } else {
340 lastChunk = chunks.size() - 2;
341 }
342
343 for (int i = 1; i < lastChunk; i++) {
344 ScriptChunk chunk = chunks.get(i);
345 if (!chunk.isOpCode() && chunk.data.length == 0) {
346 unsigned++;
347 }
348 }
349 return unsigned;
350 }
351
352 /**
353 * Checks whether a btc tx has been signed by the required number of federators.
354 * @param btcContext Btc context
355 * @param btcTx The btc tx to check
356 * @return True if was signed by the required number of federators, false otherwise
357 */
358 public static boolean hasEnoughSignatures(Context btcContext, BtcTransaction btcTx) {
359 // When the tx is constructed OP_0 are placed where signature should go.
360 // Check all OP_0 have been replaced with actual signatures in all inputs
361 Context.propagate(btcContext);
362 Script scriptSig;
363 List<ScriptChunk> chunks;
364 Script redeemScript;
365 RedeemScriptParser parser;
366 MultiSigType multiSigType;
367
368 int lastChunk;
369 for (TransactionInput input : btcTx.getInputs()) {
370 scriptSig = input.getScriptSig();
371 chunks = scriptSig.getChunks();
372 redeemScript = new Script(chunks.get(chunks.size() - 1).data);
373 parser = RedeemScriptParserFactory.get(redeemScript.getChunks());
374 multiSigType = parser.getMultiSigType();
375
376 if (multiSigType == MultiSigType.STANDARD_MULTISIG ||
377 multiSigType == MultiSigType.FAST_BRIDGE_MULTISIG
378 ) {
379 lastChunk = chunks.size() - 1;
380 } else {
381 lastChunk = chunks.size() - 2;
382 }
383
384 for (int i = 1; i < lastChunk; i++) {
385 ScriptChunk chunk = chunks.get(i);
386 if (!chunk.isOpCode() && chunk.data.length == 0) {
387 return false;
388 }
389 }
390 }
391 return true;
392 }
393
394 public static Address recoverBtcAddressFromEthTransaction(org.ethereum.core.Transaction tx, NetworkParameters networkParameters) {
395 org.ethereum.crypto.ECKey key = tx.getKey();
396 byte[] pubKey = key.getPubKey(true);
397 return BtcECKey.fromPublicOnly(pubKey).toAddress(networkParameters);
398 }
399
400 public static boolean isFreeBridgeTx(Transaction rskTx, Constants constants, ActivationConfig.ForBlock activations) {
401 RskAddress receiveAddress = rskTx.getReceiveAddress();
402 if (receiveAddress.equals(RskAddress.nullAddress())) {
403 return false;
404 }
405
406 BridgeConstants bridgeConstants = constants.getBridgeConstants();
407
408 // Temporary assumption: if areBridgeTxsFree() is true then the current federation
409 // must be the genesis federation.
410 // Once the original federation changes, txs are always paid.
411 return PrecompiledContracts.BRIDGE_ADDR.equals(receiveAddress) &&
412 !activations.isActive(ConsensusRule.ARE_BRIDGE_TXS_PAID) &&
413 rskTx.acceptTransactionSignature(constants.getChainId()) &&
414 (
415 isFromFederateMember(rskTx, bridgeConstants.getGenesisFederation()) ||
416 isFromFederationChangeAuthorizedSender(rskTx, bridgeConstants) ||
417 isFromLockWhitelistChangeAuthorizedSender(rskTx, bridgeConstants) ||
418 isFromFeePerKbChangeAuthorizedSender(rskTx, bridgeConstants)
419 );
420 }
421
422 /**
423 * Indicates if the provided tx was generated from a contract
424 * @param rskTx
425 * @return
426 */
427 public static boolean isContractTx(Transaction rskTx) {
428 // TODO: this should be refactored to provide a more robust way of checking the transaction origin
429 return rskTx.getClass() == org.ethereum.vm.program.InternalTransaction.class;
430 }
431
432 public static boolean isFromFederateMember(org.ethereum.core.Transaction rskTx, Federation federation) {
433 return federation.hasMemberWithRskAddress(rskTx.getSender().getBytes());
434 }
435
436 public static Coin getCoinFromBigInteger(BigInteger value) throws BridgeIllegalArgumentException {
437 if (value == null) {
438 throw new BridgeIllegalArgumentException("value cannot be null");
439 }
440 try {
441 return Coin.valueOf(value.longValueExact());
442 } catch(ArithmeticException e) {
443 throw new BridgeIllegalArgumentException(e.getMessage(), e);
444 }
445 }
446
447 private static boolean isFromFederationChangeAuthorizedSender(org.ethereum.core.Transaction rskTx, BridgeConstants bridgeConfiguration) {
448 AddressBasedAuthorizer authorizer = bridgeConfiguration.getFederationChangeAuthorizer();
449 return authorizer.isAuthorized(rskTx);
450 }
451
452 private static boolean isFromLockWhitelistChangeAuthorizedSender(org.ethereum.core.Transaction rskTx, BridgeConstants bridgeConfiguration) {
453 AddressBasedAuthorizer authorizer = bridgeConfiguration.getLockWhitelistChangeAuthorizer();
454 return authorizer.isAuthorized(rskTx);
455 }
456
457 private static boolean isFromFeePerKbChangeAuthorizedSender(org.ethereum.core.Transaction rskTx, BridgeConstants bridgeConfiguration) {
458 AddressBasedAuthorizer authorizer = bridgeConfiguration.getFeePerKbChangeAuthorizer();
459 return authorizer.isAuthorized(rskTx);
460 }
461
462 public static boolean validateHeightAndConfirmations(int height, int btcBestChainHeight, int acceptableConfirmationsAmount, Sha256Hash btcTxHash) throws Exception {
463 // Check there are at least N blocks on top of the supplied height
464 if (height < 0) {
465 throw new Exception("Height can't be lower than 0");
466 }
467 int confirmations = btcBestChainHeight - height + 1;
468 if (confirmations < acceptableConfirmationsAmount) {
469 logger.warn(
470 "Btc Tx {} at least {} confirmations are required, but there are only {} confirmations",
471 btcTxHash,
472 acceptableConfirmationsAmount,
473 confirmations
474 );
475 return false;
476 }
477 return true;
478 }
479
480 public static Sha256Hash calculateMerkleRoot(NetworkParameters networkParameters, byte[] pmtSerialized, Sha256Hash btcTxHash) throws VerificationException{
481 PartialMerkleTree pmt = new PartialMerkleTree(networkParameters, pmtSerialized, 0);
482 List<Sha256Hash> hashesInPmt = new ArrayList<>();
483 Sha256Hash merkleRoot = pmt.getTxnHashAndMerkleRoot(hashesInPmt);
484 if (!hashesInPmt.contains(btcTxHash)) {
485 logger.warn("Supplied Btc Tx {} is not in the supplied partial merkle tree", btcTxHash);
486 return null;
487 }
488 return merkleRoot;
489 }
490
491 public static void validateInputsCount(byte[] btcTxSerialized, boolean isActiveRskip) throws VerificationException.EmptyInputsOrOutputs {
492 if (BtcTransactionFormatUtils.getInputsCount(btcTxSerialized) == 0) {
493 if (isActiveRskip) {
494 if (BtcTransactionFormatUtils.getInputsCountForSegwit(btcTxSerialized) == 0) {
495 logger.warn("Provided btc segwit tx has no inputs");
496 // this is the exception thrown by co.rsk.bitcoinj.core.BtcTransaction#verify when there are no inputs.
497 throw new VerificationException.EmptyInputsOrOutputs();
498 }
499 } else {
500 logger.warn("Provided btc tx has no inputs ");
501 // this is the exception thrown by co.rsk.bitcoinj.core.BtcTransaction#verify when there are no inputs.
502 throw new VerificationException.EmptyInputsOrOutputs();
503 }
504 }
505 }
506
507 /**
508 * Check if the p2sh multisig scriptsig of the given input was already signed by federatorPublicKey.
509 * @param federatorPublicKey The key that may have been used to sign
510 * @param sighash the sighash that corresponds to the input
511 * @param input The input
512 * @return true if the input was already signed by the specified key, false otherwise.
513 */
514 public static boolean isInputSignedByThisFederator(BtcECKey federatorPublicKey, Sha256Hash sighash, TransactionInput input) {
515 List<ScriptChunk> chunks = input.getScriptSig().getChunks();
516 for (int j = 1; j < chunks.size() - 1; j++) {
517 ScriptChunk chunk = chunks.get(j);
518
519 if (chunk.data.length == 0) {
520 continue;
521 }
522
523 TransactionSignature sig2 = TransactionSignature.decodeFromBitcoin(chunk.data, false, false);
524
525 if (federatorPublicKey.verify(sighash, sig2)) {
526 return true;
527 }
528 }
529 return false;
530 }
531
532 public static int extractAddressVersionFromBytes(byte[] addressBytes) throws BridgeIllegalArgumentException {
533 if (addressBytes == null || addressBytes.length == 0) {
534 throw new BridgeIllegalArgumentException("Can't get an address version if the bytes are empty");
535 }
536 return addressBytes[0];
537 }
538
539 public static byte[] extractHash160FromBytes(byte[] addressBytes)
540 throws BridgeIllegalArgumentException {
541 if (addressBytes == null || addressBytes.length == 0) {
542 throw new BridgeIllegalArgumentException("Can't get an address hash160 if the bytes are empty");
543 }
544 byte[] hashBytes = new byte[20];
545 System.arraycopy(addressBytes, 1, hashBytes, 0, 20);
546 return hashBytes;
547 }
548
549 public static int getRegularPegoutTxSize(@Nonnull Federation federation) {
550 // A regular peg-out transaction has two inputs and two outputs
551 // Each input has M/N signatures and each signature is around 71 bytes long (signed sighash)
552 // The outputs are composed of the scriptPubkeyHas(or publicKeyHash)
553 // and the op_codes for the corresponding script
554 final int INPUT_MULTIPLIER = 2;
555 final int SIGNATURE_MULTIPLIER = 71;
556 final int OUTPUT_MULTIPLIER = 2;
557 final int OUTPUT_SIZE = 25;
558
559 return calculatePegoutTxSize(
560 federation,
561 INPUT_MULTIPLIER,
562 SIGNATURE_MULTIPLIER,
563 OUTPUT_MULTIPLIER,
564 OUTPUT_SIZE
565 );
566 }
567
568 public static int calculatePegoutTxSize(
569 Federation federation,
570 int inputMultiplier,
571 int signatureMultiplier,
572 int outputMultiplier,
573 int outputSize
574 ) {
575 // This data accounts for txid+vout+sequence
576 int INPUT_ADDITIONAL_DATA_SIZE = 40;
577 // This data accounts for the value+index
578 int OUTPUT_ADDITIONAL_DATA_SIZE = 9;
579 // This data accounts for the version field
580 int TX_ADDITIONAL_DATA_SIZE = 4;
581 // The added ones are to account for the data size
582 int scriptSigChunk = federation.getNumberOfSignaturesRequired() * (signatureMultiplier + 1) +
583 federation.getRedeemScript().getProgram().length + 1;
584 return TX_ADDITIONAL_DATA_SIZE +
585 (scriptSigChunk + INPUT_ADDITIONAL_DATA_SIZE) * inputMultiplier +
586 (outputSize + 1 + OUTPUT_ADDITIONAL_DATA_SIZE) * outputMultiplier;
587 }
588
589 private static Optional<Script> extractRedeemScriptFromInput(TransactionInput txInput) {
590 Script inputScript = txInput.getScriptSig();
591 List<ScriptChunk> chunks = inputScript.getChunks();
592 if (chunks == null || chunks.isEmpty()) {
593 return Optional.empty();
594 }
595
596 byte[] program = chunks.get(chunks.size() - 1).data;
597 if (program == null) {
598 return Optional.empty();
599 }
600
601 try {
602 Script redeemScript = new Script(program);
603 return Optional.of(redeemScript);
604 } catch (ScriptException e) {
605 logger.debug(
606 "[extractRedeemScriptFromInput] Failed to extract redeem script from tx input {}. {}",
607 txInput,
608 e.getMessage()
609 );
610 return Optional.empty();
611 }
612 }
613 }