/**
  * Updates the outputs on the payment contract transaction and re-signs it. The state must be
  * READY in order to call this method. The signature that is returned should be sent to the server
  * so it has the ability to broadcast the best seen payment when the channel closes or times out.
  *
  * <p>The returned signature is over the payment transaction, which we never have a valid copy of
  * and thus there is no accessor for it on this object.
  *
  * <p>To spend the whole channel increment by {@link PaymentChannelClientState#getTotalValue()} -
  * {@link PaymentChannelClientState#getValueRefunded()}
  *
  * @param size How many satoshis to increment the payment by (note: not the new total).
  * @throws ValueOutOfRangeException If size is negative or the channel does not have sufficient
  *     money in it to complete this payment.
  */
 public synchronized IncrementedPayment incrementPaymentBy(Coin size)
     throws ValueOutOfRangeException {
   checkState(state == State.READY);
   checkNotExpired();
   checkNotNull(size); // Validity of size will be checked by makeUnsignedChannelContract.
   if (size.signum() < 0) throw new ValueOutOfRangeException("Tried to decrement payment");
   Coin newValueToMe = valueToMe.subtract(size);
   if (newValueToMe.compareTo(Transaction.MIN_NONDUST_OUTPUT) < 0 && newValueToMe.signum() > 0) {
     log.info(
         "New value being sent back as change was smaller than minimum nondust output, sending all");
     size = valueToMe;
     newValueToMe = Coin.ZERO;
   }
   if (newValueToMe.signum() < 0)
     throw new ValueOutOfRangeException(
         "Channel has too little money to pay " + size + " satoshis");
   Transaction tx = makeUnsignedChannelContract(newValueToMe);
   log.info("Signing new payment tx {}", tx);
   Transaction.SigHash mode;
   // If we spent all the money we put into this channel, we (by definition) don't care what the
   // outputs are, so
   // we sign with SIGHASH_NONE to let the server do what it wants.
   if (newValueToMe.equals(Coin.ZERO)) mode = Transaction.SigHash.NONE;
   else mode = Transaction.SigHash.SINGLE;
   TransactionSignature sig = tx.calculateSignature(0, myKey, multisigScript, mode, true);
   valueToMe = newValueToMe;
   updateChannelInWallet();
   IncrementedPayment payment = new IncrementedPayment();
   payment.signature = sig;
   payment.amount = size;
   return payment;
 }
Example #2
0
 public TransactionOutput(
     NetworkParameters params, @Nullable Transaction parent, Coin value, byte[] scriptBytes) {
   super(params);
   // Negative values obviously make no sense, except for -1 which is used as a sentinel value when
   // calculating
   // SIGHASH_SINGLE signatures, so unfortunately we have to allow that here.
   checkArgument(
       value.signum() >= 0 || value.equals(Coin.NEGATIVE_SATOSHI), "Negative values not allowed");
   checkArgument(
       value.compareTo(NetworkParameters.MAX_MONEY) < 0,
       "Values larger than MAX_MONEY not allowed");
   this.value = value.value;
   this.scriptBytes = scriptBytes;
   parentTransaction = parent;
   availableForSpending = true;
   length = 8 + VarInt.sizeOf(scriptBytes.length) + scriptBytes.length;
 }
 PaymentChannelServerState(
     StoredServerChannel storedServerChannel, Wallet wallet, TransactionBroadcaster broadcaster)
     throws VerificationException {
   synchronized (storedServerChannel) {
     this.wallet = checkNotNull(wallet);
     this.broadcaster = checkNotNull(broadcaster);
     this.multisigContract = checkNotNull(storedServerChannel.contract);
     this.multisigScript = multisigContract.getOutput(0).getScriptPubKey();
     this.clientKey = ECKey.fromPublicOnly(multisigScript.getChunks().get(1).data);
     this.clientOutput = checkNotNull(storedServerChannel.clientOutput);
     this.refundTransactionUnlockTimeSecs = storedServerChannel.refundTransactionUnlockTimeSecs;
     this.serverKey = checkNotNull(storedServerChannel.myKey);
     this.totalValue = multisigContract.getOutput(0).getValue();
     this.bestValueToMe = checkNotNull(storedServerChannel.bestValueToMe);
     this.bestValueSignature = storedServerChannel.bestValueSignature;
     checkArgument(bestValueToMe.equals(Coin.ZERO) || bestValueSignature != null);
     this.storedServerChannel = storedServerChannel;
     storedServerChannel.state = this;
     this.state = State.READY;
   }
 }
  /**
   * Called when the client provides us with a new signature and wishes to increment total payment
   * by size. Verifies the provided signature and only updates values if everything checks out. If
   * the new refundSize is not the lowest we have seen, it is simply ignored.
   *
   * @param refundSize How many satoshis of the original contract are refunded to the client (the
   *     rest are ours)
   * @param signatureBytes The new signature spending the multi-sig contract to a new payment
   *     transaction
   * @throws VerificationException If the signature does not verify or size is out of range (incl
   *     being rejected by the network as dust).
   * @return true if there is more value left on the channel, false if it is now fully used up.
   */
  public synchronized boolean incrementPayment(Coin refundSize, byte[] signatureBytes)
      throws VerificationException, ValueOutOfRangeException, InsufficientMoneyException {
    checkState(state == State.READY);
    checkNotNull(refundSize);
    checkNotNull(signatureBytes);
    TransactionSignature signature = TransactionSignature.decodeFromBitcoin(signatureBytes, true);
    // We allow snapping to zero for the payment amount because it's treated specially later, but
    // not less than
    // the dust level because that would prevent the transaction from being relayed/mined.
    final boolean fullyUsedUp = refundSize.equals(Coin.ZERO);
    if (refundSize.compareTo(clientOutput.getMinNonDustValue()) < 0 && !fullyUsedUp)
      throw new ValueOutOfRangeException(
          "Attempt to refund negative value or value too small to be accepted by the network");
    Coin newValueToMe = totalValue.subtract(refundSize);
    if (newValueToMe.signum() < 0)
      throw new ValueOutOfRangeException("Attempt to refund more than the contract allows.");
    if (newValueToMe.compareTo(bestValueToMe) < 0)
      throw new ValueOutOfRangeException("Attempt to roll back payment on the channel.");

    // Get the wallet's copy of the multisigContract (ie with confidence information), if this is
    // null, the wallet
    // was not connected to the peergroup when the contract was broadcast (which may cause issues
    // down the road, and
    // disables our double-spend check next)
    Transaction walletContract = wallet.getTransaction(multisigContract.getHash());
    checkNotNull(
        walletContract,
        "Wallet did not contain multisig contract {} after state was marked READY",
        multisigContract.getHash());

    // Note that we check for DEAD state here, but this test is essentially useless in production
    // because we will
    // miss most double-spends due to bloom filtering right now anyway. This will eventually fixed
    // by network-wide
    // double-spend notifications, so we just wait instead of attempting to add all dependant
    // outpoints to our bloom
    // filters (and probably missing lots of edge-cases).
    if (walletContract.getConfidence().getConfidenceType()
        == TransactionConfidence.ConfidenceType.DEAD) {
      close();
      throw new VerificationException("Multisig contract was double-spent");
    }

    Transaction.SigHash mode;
    // If the client doesn't want anything back, they shouldn't sign any outputs at all.
    if (fullyUsedUp) mode = Transaction.SigHash.NONE;
    else mode = Transaction.SigHash.SINGLE;

    if (signature.sigHashMode() != mode || !signature.anyoneCanPay())
      throw new VerificationException(
          "New payment signature was not signed with the right SIGHASH flags.");

    Wallet.SendRequest req = makeUnsignedChannelContract(newValueToMe);
    // Now check the signature is correct.
    // Note that the client must sign with SIGHASH_{SINGLE/NONE} | SIGHASH_ANYONECANPAY to allow us
    // to add additional
    // inputs (in case we need to add significant fee, or something...) and any outputs we want to
    // pay to.
    Sha256Hash sighash = req.tx.hashForSignature(0, multisigScript, mode, true);

    if (!clientKey.verify(sighash, signature))
      throw new VerificationException("Signature does not verify on tx\n" + req.tx);
    bestValueToMe = newValueToMe;
    bestValueSignature = signatureBytes;
    updateChannelInWallet();
    return !fullyUsedUp;
  }