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 }