docs: public static Script createP2SHMultiSigInputScript(@Nullable List<TransactionSignature> signatures,
                                                         Script multisigProgram) {
          List<byte[]> sigs = new ArrayList<>();
          if (signatures == null) {
              // create correct number of empty signatures
              int numSigs = multisigProgram.getNumberOfSignaturesRequiredToSpend();
              for (int i = 0; i < numSigs; i++)
                  sigs.add(new byte[]{});
          } else {
              for (TransactionSignature signature : signatures) {
                  sigs.add(signature.encodeToBitcoin());
              }
          }
          return createMultiSigInputScriptBytes(sigs, multisigProgram.getProgram());
      }
  
      /**
       * Create a program that satisfies an OP_CHECKMULTISIG program, using pre-encoded signatures. 
       * Optionally, appends the script program bytes if spending a P2SH output.
       */
      public static Script createMultiSigInputScriptBytes(List<byte[]> signatures, @Nullable byte[] multisigProgramBytes) {
          checkArgument(signatures.size() <= 16);
          ScriptBuilder builder = new ScriptBuilder();
          builder.smallNum(0);  // Work around a bug in CHECKMULTISIG that is now a required part of the protocol.
          for (byte[] signature : signatures)
              builder.data(signature);
          if (multisigProgramBytes!= null)
              builder.data(multisigProgramBytes);
          return builder.build();
      }
  
      /**
       * Returns a copy of the given scriptSig with the signature inserted in the given position.
       *
       * This function assumes that any missing sigs have OP_0 placeholders. If given scriptSig already has all the signatures
       * in place, IllegalArgumentException will be thrown.
       *
       * @param targetIndex where to insert the signature
       * @param sigsPrefixCount how many items to copy verbatim (e.g. initial OP_0 for multisig)
       * @param sigsSuffixCount how many items to copy verbatim at end (e.g. redeemScript for P2SH)
       */
      public static Script updateScriptWithSignature(Script scriptSig, byte[] signature, int targetIndex,
                                                     int sigsPrefixCount, int sigsSuffixCount) {
          ScriptBuilder builder = new ScriptBuilder();
          List<ScriptChunk> inputChunks = scriptSig.getChunks();
          int totalChunks = inputChunks.size();
  
          // Check if we have a place to insert, otherwise just return given scriptSig unchanged.
          // We assume here that OP_0 placeholders always go after the sigs, so
          // to find if we have sigs missing, we can just check the chunk in latest sig position
          boolean hasMissingSigs = inputChunks.get(totalChunks - sigsSuffixCount - 1).equalsOpCode(OP_0);
          checkArgument(hasMissingSigs, "ScriptSig is already filled with signatures");
  
          // copy the prefix
          for (ScriptChunk chunk: inputChunks.subList(0, sigsPrefixCount))
              builder.addChunk(chunk);
  
          // copy the sigs
          int pos = 0;
          boolean inserted = false;
          for (ScriptChunk chunk: inputChunks.subList(sigsPrefixCount, totalChunks - sigsSuffixCount)) {
              if (pos == targetIndex) {
                  inserted = true;
                  builder.data(signature);
                  pos++;
              }
              if (!chunk.equalsOpCode(OP_0)) {
                  builder.addChunk(chunk);
                  pos++;
              }
          }
  
          // add OP_0's if needed, since we skipped them in the previous loop
          while (pos < totalChunks - sigsPrefixCount - sigsSuffixCount) {
              if (pos == targetIndex) {
                  inserted = true;
                  builder.data(signature);
              }
              else {
                  builder.addChunk(new ScriptChunk(OP_0, null));
              }
              pos++;
          }
  
          // copy the suffix
          for (ScriptChunk chunk: inputChunks.subList(totalChunks - sigsSuffixCount, totalChunks))
              builder.addChunk(chunk);
  
          checkState(inserted);
          return builder.build();
      }
  
      /** Creates a scriptPubKey that encodes payment to the given raw public key. */
      public static Script createP2PKOutputScript(byte[] pubKey) {
          return new ScriptBuilder().data(pubKey).op(OP_CHECKSIG).build();
      }
  
      /** Creates a scriptPubKey that encodes payment to the given raw public key. */
      public static Script createP2PKOutputScript(ECKey pubKey) {
          return createP2PKOutputScript(pubKey.getPubKey());
      }
  
      /**
       * Creates a scriptPubKey that sends to the given public key hash.
       */
      public static Script createP2PKHOutputScript(byte[] hash) {
          checkArgument(hash.length == LegacyAddress.LENGTH);
          ScriptBuilder builder = new ScriptBuilder();
          builder.op(OP_DUP);
          builder.op(OP_HASH160);
          builder.data(hash);
          builder.op(OP_EQUALVERIFY);
          builder.op(OP_CHECKSIG);
          return builder.build();
      }
  
      /**
       * Creates a scriptPubKey that sends to the given public key.
       */
      public static Script createP2PKHOutputScript(ECKey key) {
          checkArgument(key.isCompressed());
          return createP2PKHOutputScript(key.getPubKeyHash());
      }
  
      /**
       * Creates a segwit scriptPubKey that sends to the given public key hash.
       */
      public static Script createP2WPKHOutputScript(byte[] hash) {
          checkArgument(hash.length == SegwitAddress.WITNESS_PROGRAM_LENGTH_PKH);
          return new ScriptBuilder().smallNum(0).data(hash).build();
      }
  
      /**
       * Creates a segwit scriptPubKey that sends to the given public key.
       */
      public static Script createP2WPKHOutputScript(ECKey key) {
          checkArgument(key.isCompressed());
          return createP2WPKHOutputScript(key.getPubKeyHash());
      }
  
      /**
       * Creates a scriptPubKey that sends to the given script hash. Read
       * <a href="https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki">BIP 16</a> to learn more about this
       * kind of script.
       */
      public static Script createP2SHOutputScript(byte[] hash) {
          checkArgument(hash.length == 20);
          return new ScriptBuilder().op(OP_HASH160).data(hash).op(OP_EQUAL).build();
      }
  
      /**
       * Creates a scriptPubKey for the given redeem script.
       */
      public static Script createP2SHOutputScript(Script redeemScript) {
          byte[] hash = Utils.sha256hash160(redeemScript.getProgram());
          return ScriptBuilder.createP2SHOutputScript(hash);
      }
  
      /**
       * Creates a segwit scriptPubKey that sends to the given script hash.
       */
      public static Script createP2WSHOutputScript(byte[] hash) {
          checkArgument(hash.length == SegwitAddress.WITNESS_PROGRAM_LENGTH_SH);
          return new ScriptBuilder().smallNum(0).data(hash).build();
      }
  
      /**
       * Creates a segwit scriptPubKey for the given redeem script.
       */
      public static Script createP2WSHOutputScript(Script redeemScript) {
          byte[] hash = Sha256Hash.hash(redeemScript.getProgram());
          return ScriptBuilder.createP2WSHOutputScript(hash);
      }
  
      /**
       * Creates a P2SH output script with given public keys and threshold. Given public keys will be placed in
       * redeem script in the lexicographical sorting order.
       */
      public static Script createP2SHOutputScript(int threshold, List<ECKey> pubkeys) {
          Script redeemScript = createRedeemScript(threshold, pubkeys);
          return createP2SHOutputScript(redeemScript);
      }
  
      /**
       * Creates redeem script with given public keys and threshold. Given public keys will be placed in
       * redeem script in the lexicographical sorting order.
       */
      public static Script createRedeemScript(int threshold, List<ECKey> pubkeys) {
          pubkeys = new ArrayList<>(pubkeys);
          Collections.sort(pubkeys, ECKey.PUBKEY_COMPARATOR);
          return ScriptBuilder.createMultiSigOutputScript(threshold, pubkeys);
      }
  
      /**
       * Creates a script of the form OP_RETURN [data]. This feature allows you to attach a small piece of data (like
       * a hash of something stored elsewhere) to a zero valued output which can never be spent and thus does not pollute
       * the ledger.
       */
      public static Script createOpReturnScript(byte[] data) {
          checkArgument(data.length <= 80);
          return new ScriptBuilder().op(OP_RETURN).data(data).build();
      }
  
      public static Script createCLTVPaymentChannelOutput(BigInteger time, ECKey from, ECKey to) {
          byte[] timeBytes = Utils.reverseBytes(Utils.encodeMPI(time, false));
          if (timeBytes.length > 5) {
              throw new RuntimeException("Time too large to encode as 5-byte int");
          }
          return new ScriptBuilder().op(OP_IF)
                  .data(to.getPubKey()).op(OP_CHECKSIGVERIFY)
                  .op(OP_ELSE)
                  .data(timeBytes).op(OP_CHECKLOCKTIMEVERIFY).op(OP_DROP)
                  .op(OP_ENDIF)
                  .data(from.getPubKey()).op(OP_CHECKSIG).build();
      }
  
      public static Script createCLTVPaymentChannelRefund(TransactionSignature signature) {
          ScriptBuilder builder = new ScriptBuilder();
          builder.data(signature.encodeToBitcoin());
          builder.data(new byte[] { 0 }); // Use the CHECKLOCKTIMEVERIFY if branch
          return builder.build();
      }
  
      public static Script createCLTVPaymentChannelP2SHRefund(TransactionSignature signature, Script redeemScript) {
          ScriptBuilder builder = new ScriptBuilder();
          builder.data(signature.encodeToBitcoin());
          builder.data(new byte[] { 0 }); // Use the CHECKLOCKTIMEVERIFY if branch
          builder.data(redeemScript.getProgram());
          return builder.build();
      }
  
      public static Script createCLTVPaymentChannelP2SHInput(byte[] from, byte[] to, Script redeemScript) {
          ScriptBuilder builder = new ScriptBuilder();
          builder.data(from);
          builder.data(to);
          builder.smallNum(1); // Use the CHECKLOCKTIMEVERIFY if branch
          builder.data(redeemScript.getProgram());
          return builder.build();
      }
  
      public static Script createCLTVPaymentChannelInput(TransactionSignature from, TransactionSignature to) {
          return createCLTVPaymentChannelInput(from.encodeToBitcoin(), to.encodeToBitcoin());
      }
  
      public static Script createCLTVPaymentChannelInput(byte[] from, byte[] to) {
          ScriptBuilder builder = new ScriptBuilder();
          builder.data(from);
          builder.data(to);
          builder.smallNum(1); // Use the CHECKLOCKTIMEVERIFY if branch
          return builder.build();
      }
  }