/**
   * Called when the client provides the multi-sig contract. Checks that the previously-provided
   * refund transaction spends this transaction (because we will use it as a base to create payment
   * transactions) as well as output value and form (ie it is a 2-of-2 multisig to the correct
   * keys).
   *
   * @param multisigContract The provided multisig contract. Do not mutate this object after this
   *     call.
   * @return A future which completes when the provided multisig contract successfully broadcasts,
   *     or throws if the broadcast fails for some reason Note that if the network simply rejects
   *     the transaction, this future will never complete, a timeout should be used.
   * @throws VerificationException If the provided multisig contract is not well-formed or does not
   *     meet previously-specified parameters
   */
  public synchronized ListenableFuture<PaymentChannelServerState> provideMultiSigContract(
      final Transaction multisigContract) throws VerificationException {
    checkNotNull(multisigContract);
    checkState(state == State.WAITING_FOR_MULTISIG_CONTRACT);
    try {
      multisigContract.verify();
      this.multisigContract = multisigContract;
      this.multisigScript = multisigContract.getOutput(0).getScriptPubKey();

      // Check that multisigContract's first output is a 2-of-2 multisig to the correct pubkeys in
      // the correct order
      final Script expectedScript =
          ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(clientKey, serverKey));
      if (!Arrays.equals(multisigScript.getProgram(), expectedScript.getProgram()))
        throw new VerificationException(
            "Multisig contract's first output was not a standard 2-of-2 multisig to client and server in that order.");

      this.totalValue = multisigContract.getOutput(0).getValue();
      if (this.totalValue.signum() <= 0)
        throw new VerificationException(
            "Not accepting an attempt to open a contract with zero value.");
    } catch (VerificationException e) {
      // We couldn't parse the multisig transaction or its output.
      log.error("Provided multisig contract did not verify: {}", multisigContract.toString());
      throw e;
    }
    log.info("Broadcasting multisig contract: {}", multisigContract);
    state = State.WAITING_FOR_MULTISIG_ACCEPTANCE;
    final SettableFuture<PaymentChannelServerState> future = SettableFuture.create();
    Futures.addCallback(
        broadcaster.broadcastTransaction(multisigContract).future(),
        new FutureCallback<Transaction>() {
          @Override
          public void onSuccess(Transaction transaction) {
            log.info(
                "Successfully broadcast multisig contract {}. Channel now open.",
                transaction.getHashAsString());
            try {
              // Manually add the multisigContract to the wallet, overriding the isRelevant checks
              // so we can track
              // it and check for double-spends later
              wallet.receivePending(multisigContract, null, true);
            } catch (VerificationException e) {
              throw new RuntimeException(
                  e); // Cannot happen, we already called multisigContract.verify()
            }
            state = State.READY;
            future.set(PaymentChannelServerState.this);
          }

          @Override
          public void onFailure(Throwable throwable) {
            // Couldn't broadcast the transaction for some reason.
            log.error("Broadcast multisig contract failed", throwable);
            state = State.ERROR;
            future.setException(throwable);
          }
        });
    return future;
  }
 /** Returns true if the tx is a valid settlement transaction. */
 public synchronized boolean isSettlementTransaction(Transaction tx) {
   try {
     tx.verify();
     tx.getInput(0).verify(multisigContract.getOutput(0));
     return true;
   } catch (VerificationException e) {
     return false;
   }
 }
    @Test
    public void nemesisTransactionsAreVerifiable() {
      // Arrange:
      final Block block = this.loadNemesisBlock();

      // Assert:
      for (final Transaction transaction : block.getTransactions()) {
        Assert.assertThat(transaction.verify(), IsEqual.equalTo(true));
      }
    }
Ejemplo n.º 4
0
 @Override
 public void broadcast(Transaction transaction) throws NxtException.ValidationException {
   if (!transaction.verify()) {
     throw new NxtException.ValidationException("Transaction signature verification failed");
   }
   List<Transaction> validTransactions =
       processTransactions(Collections.singletonList((TransactionImpl) transaction), true);
   if (validTransactions.contains(transaction)) {
     nonBroadcastedTransactions.put(transaction.getId(), (TransactionImpl) transaction);
     Logger.logDebugMessage("Accepted new transaction " + transaction.getStringId());
   } else {
     Logger.logDebugMessage("Rejecting double spending transaction " + transaction.getStringId());
     throw new NxtException.ValidationException("Double spending transaction");
   }
 }
Ejemplo n.º 5
0
 /**
  * Create a payment message. This wraps up transaction data along with anything else useful for
  * making a payment.
  *
  * @param transactions transactions to include with the payment message
  * @param refundOutputs list of outputs to refund coins to, or null
  * @param memo arbitrary, user readable memo, or null if none
  * @param merchantData arbitrary merchant data, or null if none
  * @return created payment message
  */
 public static Protos.Payment createPaymentMessage(
     List<Transaction> transactions,
     @Nullable List<Protos.Output> refundOutputs,
     @Nullable String memo,
     @Nullable byte[] merchantData) {
   Protos.Payment.Builder builder = Protos.Payment.newBuilder();
   for (Transaction transaction : transactions) {
     transaction.verify();
     builder.addTransactions(ByteString.copyFrom(transaction.unsafeBitcoinSerialize()));
   }
   if (refundOutputs != null) {
     for (Protos.Output output : refundOutputs) builder.addRefundTo(output);
   }
   if (memo != null) builder.setMemo(memo);
   if (merchantData != null) builder.setMerchantData(ByteString.copyFrom(merchantData));
   return builder.build();
 }
Ejemplo n.º 6
0
 /**
  * Checks the block contents
  *
  * @throws VerificationException
  */
 public void verifyTransactions() throws VerificationException {
   // Now we need to check that the body of the block actually matches the headers. The network
   // won't generate
   // an invalid block, but if we didn't validate this then an untrusted man-in-the-middle could
   // obtain the next
   // valid block from the network and simply replace the transactions in it with their own
   // fictional
   // transactions that reference spent or non-existant inputs.
   if (transactions.isEmpty()) throw new VerificationException("Block had no transactions");
   maybeParseTransactions();
   if (this.getOptimalEncodingMessageSize() > MAX_BLOCK_SIZE)
     throw new VerificationException("Block larger than MAX_BLOCK_SIZE");
   checkTransactions();
   checkMerkleRoot();
   checkSigOps();
   for (Transaction transaction : transactions) transaction.verify();
 }
  /**
   * Called when the client provides the refund transaction. The refund transaction must have one
   * input from the multisig contract (that we don't have yet) and one output that the client
   * creates to themselves. This object will later be modified when we start getting paid.
   *
   * @param refundTx The refund transaction, this object will be mutated when payment is
   *     incremented.
   * @param clientMultiSigPubKey The client's pubkey which is required for the multisig output
   * @return Our signature that makes the refund transaction valid
   * @throws VerificationException If the transaction isnt valid or did not meet the requirements of
   *     a refund transaction.
   */
  public synchronized byte[] provideRefundTransaction(
      Transaction refundTx, byte[] clientMultiSigPubKey) throws VerificationException {
    checkNotNull(refundTx);
    checkNotNull(clientMultiSigPubKey);
    checkState(state == State.WAITING_FOR_REFUND_TRANSACTION);
    log.info("Provided with refund transaction: {}", refundTx);
    // Do a few very basic syntax sanity checks.
    refundTx.verify();
    // Verify that the refund transaction has a single input (that we can fill to sign the multisig
    // output).
    if (refundTx.getInputs().size() != 1)
      throw new VerificationException("Refund transaction does not have exactly one input");
    // Verify that the refund transaction has a time lock on it and a sequence number of zero.
    if (refundTx.getInput(0).getSequenceNumber() != 0)
      throw new VerificationException("Refund transaction's input's sequence number is non-0");
    if (refundTx.getLockTime() < minExpireTime)
      throw new VerificationException("Refund transaction has a lock time too soon");
    // Verify the transaction has one output (we don't care about its contents, its up to the
    // client)
    // Note that because we sign with SIGHASH_NONE|SIGHASH_ANYOENCANPAY the client can later add
    // more outputs and
    // inputs, but we will need only one output later to create the paying transactions
    if (refundTx.getOutputs().size() != 1)
      throw new VerificationException("Refund transaction does not have exactly one output");

    refundTransactionUnlockTimeSecs = refundTx.getLockTime();

    // Sign the refund tx with the scriptPubKey and return the signature. We don't have the spending
    // transaction
    // so do the steps individually.
    clientKey = ECKey.fromPublicOnly(clientMultiSigPubKey);
    Script multisigPubKey =
        ScriptBuilder.createMultiSigOutputScript(2, ImmutableList.of(clientKey, serverKey));
    // We are really only signing the fact that the transaction has a proper lock time and don't
    // care about anything
    // else, so we sign SIGHASH_NONE and SIGHASH_ANYONECANPAY.
    TransactionSignature sig =
        refundTx.calculateSignature(0, serverKey, multisigPubKey, Transaction.SigHash.NONE, true);
    log.info("Signed refund transaction.");
    this.clientOutput = refundTx.getOutput(0);
    state = State.WAITING_FOR_MULTISIG_CONTRACT;
    return sig.encodeToBitcoin();
  }
Ejemplo n.º 8
0
 /**
  * Generates a Payment message based on the information in the PaymentRequest. Provide
  * transactions built by the wallet. If the PaymentRequest did not specify a payment_url, returns
  * null.
  *
  * @param txns list of transactions to be included with the Payment message.
  * @param refundAddr will be used by the merchant to send money back if there was a problem.
  * @param memo is a message to include in the payment message sent to the merchant.
  */
 public @Nullable Protos.Payment getPayment(
     List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo)
     throws IOException {
   if (!paymentDetails.hasPaymentUrl()) return null;
   Protos.Payment.Builder payment = Protos.Payment.newBuilder();
   if (paymentDetails.hasMerchantData()) payment.setMerchantData(paymentDetails.getMerchantData());
   if (refundAddr != null) {
     Protos.Output.Builder refundOutput = Protos.Output.newBuilder();
     refundOutput.setAmount(totalValue.longValue());
     refundOutput.setScript(
         ByteString.copyFrom(ScriptBuilder.createOutputScript(refundAddr).getProgram()));
     payment.addRefundTo(refundOutput);
   }
   if (memo != null) {
     payment.setMemo(memo);
   }
   for (Transaction txn : txns) {
     txn.verify();
     ByteArrayOutputStream o = new ByteArrayOutputStream();
     txn.bitcoinSerialize(o);
     payment.addTransactions(ByteString.copyFrom(o.toByteArray()));
   }
   return payment.build();
 }
  /**
   * Closes this channel and broadcasts the highest value payment transaction on the network.
   *
   * <p>This will set the state to {@link State#CLOSED} if the transaction is successfully broadcast
   * on the network. If we fail to broadcast for some reason, the state is set to {@link
   * State#ERROR}.
   *
   * <p>If the current state is before {@link State#READY} (ie we have not finished initializing the
   * channel), we simply set the state to {@link State#CLOSED} and let the client handle getting its
   * refund transaction confirmed.
   *
   * @return a future which completes when the provided multisig contract successfully broadcasts,
   *     or throws if the broadcast fails for some reason. Note that if the network simply rejects
   *     the transaction, this future will never complete, a timeout should be used.
   * @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than
   *     it is worth.
   */
  public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException {
    if (storedServerChannel != null) {
      StoredServerChannel temp = storedServerChannel;
      storedServerChannel = null;
      StoredPaymentChannelServerStates channels =
          (StoredPaymentChannelServerStates)
              wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
      channels.closeChannel(
          temp); // May call this method again for us (if it wasn't the original caller)
      if (state.compareTo(State.CLOSING) >= 0) return closedFuture;
    }

    if (state.ordinal() < State.READY.ordinal()) {
      log.error("Attempt to settle channel in state " + state);
      state = State.CLOSED;
      closedFuture.set(null);
      return closedFuture;
    }
    if (state != State.READY) {
      // TODO: What is this codepath for?
      log.warn("Failed attempt to settle a channel in state " + state);
      return closedFuture;
    }
    Transaction tx = null;
    try {
      Wallet.SendRequest req = makeUnsignedChannelContract(bestValueToMe);
      tx = req.tx;
      // Provide a throwaway signature so that completeTx won't complain out about unsigned inputs
      // it doesn't
      // know how to sign. Note that this signature does actually have to be valid, so we can't use
      // a dummy
      // signature to save time, because otherwise completeTx will try to re-sign it to make it
      // valid and then
      // die. We could probably add features to the SendRequest API to make this a bit more
      // efficient.
      signMultisigInput(tx, Transaction.SigHash.NONE, true);
      // Let wallet handle adding additional inputs/fee as necessary.
      req.shuffleOutputs = false;
      req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
      wallet.completeTx(req); // TODO: Fix things so shuffling is usable.
      feePaidForPayment = req.tx.getFee();
      log.info("Calculated fee is {}", feePaidForPayment);
      if (feePaidForPayment.compareTo(bestValueToMe) > 0) {
        final String msg =
            String.format(
                Locale.US,
                "Had to pay more in fees (%s) than the channel was worth (%s)",
                feePaidForPayment,
                bestValueToMe);
        throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
      }
      // Now really sign the multisig input.
      signMultisigInput(tx, Transaction.SigHash.ALL, false);
      // Some checks that shouldn't be necessary but it can't hurt to check.
      tx.verify(); // Sanity check syntax.
      for (TransactionInput input : tx.getInputs())
        input.verify(); // Run scripts and ensure it is valid.
    } catch (InsufficientMoneyException e) {
      throw e; // Don't fall through.
    } catch (Exception e) {
      log.error(
          "Could not verify self-built tx\nMULTISIG {}\nCLOSE {}",
          multisigContract,
          tx != null ? tx : "");
      throw new RuntimeException(e); // Should never happen.
    }
    state = State.CLOSING;
    log.info("Closing channel, broadcasting tx {}", tx);
    // The act of broadcasting the transaction will add it to the wallet.
    ListenableFuture<Transaction> future = broadcaster.broadcastTransaction(tx).future();
    Futures.addCallback(
        future,
        new FutureCallback<Transaction>() {
          @Override
          public void onSuccess(Transaction transaction) {
            log.info("TX {} propagated, channel successfully closed.", transaction.getHash());
            state = State.CLOSED;
            closedFuture.set(transaction);
          }

          @Override
          public void onFailure(Throwable throwable) {
            log.error("Failed to settle channel, could not broadcast", throwable);
            state = State.ERROR;
            closedFuture.setException(throwable);
          }
        });
    return closedFuture;
  }
Ejemplo n.º 10
0
  @Test
  public void dataDrivenInvalidTransactions() throws Exception {
    BufferedReader in =
        new BufferedReader(
            new InputStreamReader(
                getClass().getResourceAsStream("tx_invalid.json"), Charset.forName("UTF-8")));

    NetworkParameters params = TestNet3Params.get();

    // Poor man's (aka. really, really poor) JSON parser (because pulling in a lib for this is
    // probably overkill)
    List<JSONObject> tx = new ArrayList<JSONObject>(1);
    in.read(); // remove first [
    StringBuffer buffer = new StringBuffer(1000);
    while (in.ready()) {
      String line = in.readLine();
      if (line == null || line.equals("")) continue;
      buffer.append(line);
      if (line.equals("]") && buffer.toString().equals("]") && !in.ready()) break; // ignore last ]
      boolean isFinished = appendToList(tx, buffer);
      while (tx.size() > 0
          && tx.get(0).isList()
          && tx.get(0).list.size() == 1
          && tx.get(0).list.get(0).isString()) tx.remove(0);
      if (isFinished && tx.size() == 1 && tx.get(0).list.size() == 3) {
        HashMap<TransactionOutPoint, Script> scriptPubKeys =
            new HashMap<TransactionOutPoint, Script>();
        for (JSONObject input : tx.get(0).list.get(0).list) {
          String hash = input.list.get(0).string;
          int index = input.list.get(1).integer;
          String script = input.list.get(2).string;
          Sha256Hash sha256Hash =
              new Sha256Hash(Hex.decode(hash.getBytes(Charset.forName("UTF-8"))));
          scriptPubKeys.put(
              new TransactionOutPoint(params, index, sha256Hash), parseScriptString(script));
        }

        byte[] bytes = tx.get(0).list.get(1).string.getBytes(Charset.forName("UTF-8"));
        Transaction transaction = new Transaction(params, Hex.decode(bytes));
        boolean enforceP2SH = tx.get(0).list.get(2).booleanValue;
        assertTrue(tx.get(0).list.get(2).isBoolean());

        boolean valid = true;
        try {
          transaction.verify();
        } catch (VerificationException e) {
          valid = false;
        }

        // The reference client checks this case in CheckTransaction, but we leave it to
        // later where we will see an attempt to double-spend, so we explicitly check here
        HashSet<TransactionOutPoint> set = new HashSet<TransactionOutPoint>();
        for (TransactionInput input : transaction.getInputs()) {
          if (set.contains(input.getOutpoint())) valid = false;
          set.add(input.getOutpoint());
        }

        for (int i = 0; i < transaction.getInputs().size() && valid; i++) {
          TransactionInput input = transaction.getInputs().get(i);
          assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
          try {
            input
                .getScriptSig()
                .correctlySpends(
                    transaction, i, scriptPubKeys.get(input.getOutpoint()), enforceP2SH);
          } catch (VerificationException e) {
            valid = false;
          }
        }

        if (valid) fail();

        tx.clear();
      }
    }
    in.close();
  }
Ejemplo n.º 11
0
  @Test
  public void dataDrivenValidTransactions() throws Exception {
    BufferedReader in =
        new BufferedReader(
            new InputStreamReader(
                getClass().getResourceAsStream("tx_valid.json"), Charset.forName("UTF-8")));

    NetworkParameters params = TestNet3Params.get();

    // Poor man's (aka. really, really poor) JSON parser (because pulling in a lib for this is
    // probably not overkill)
    int lineNum = -1;
    List<JSONObject> tx = new ArrayList<JSONObject>(3);
    in.read(); // remove first [
    StringBuffer buffer = new StringBuffer(1000);
    while (in.ready()) {
      lineNum++;
      String line = in.readLine();
      if (line == null || line.equals("")) continue;
      buffer.append(line);
      if (line.equals("]") && buffer.toString().equals("]") && !in.ready()) break;
      boolean isFinished = appendToList(tx, buffer);
      while (tx.size() > 0
          && tx.get(0).isList()
          && tx.get(0).list.size() == 1
          && tx.get(0).list.get(0).isString()) tx.remove(0); // ignore last ]
      if (isFinished && tx.size() == 1 && tx.get(0).list.size() == 3) {
        Transaction transaction = null;
        try {
          HashMap<TransactionOutPoint, Script> scriptPubKeys =
              new HashMap<TransactionOutPoint, Script>();
          for (JSONObject input : tx.get(0).list.get(0).list) {
            String hash = input.list.get(0).string;
            int index = input.list.get(1).integer;
            String script = input.list.get(2).string;
            Sha256Hash sha256Hash =
                new Sha256Hash(Hex.decode(hash.getBytes(Charset.forName("UTF-8"))));
            scriptPubKeys.put(
                new TransactionOutPoint(params, index, sha256Hash), parseScriptString(script));
          }

          byte[] bytes = tx.get(0).list.get(1).string.getBytes(Charset.forName("UTF-8"));
          transaction = new Transaction(params, Hex.decode(bytes));
          boolean enforceP2SH = tx.get(0).list.get(2).booleanValue;
          assertTrue(tx.get(0).list.get(2).isBoolean());

          transaction.verify();

          for (int i = 0; i < transaction.getInputs().size(); i++) {
            TransactionInput input = transaction.getInputs().get(i);
            if (input.getOutpoint().getIndex() == 0xffffffffL) input.getOutpoint().setIndex(-1);
            assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
            input
                .getScriptSig()
                .correctlySpends(
                    transaction, i, scriptPubKeys.get(input.getOutpoint()), enforceP2SH);
          }
          tx.clear();
        } catch (Exception e) {
          System.err.println("Exception processing line " + lineNum + ": " + line);
          if (transaction != null) System.err.println(transaction);
          throw e;
        }
      }
    }
    in.close();
  }