Example #1
0
PendingApproval.prototype.recreateAndSignTransaction = function(params, callback) {
  params = _.extend({}, params);
  common.validateParams(params, ['txHex'], [], callback);

  var transaction = Transaction.fromHex(params.txHex);
  if (!transaction.outs) {
    throw new Error('transaction had no outputs or failed to parse successfully');
  }

  var network = networks[common.getNetwork()];
  params.recipients = {};

  var self = this;

  return Q()
  .then(function() {
    if (self.info().transactionRequest.recipients) {
      // recipients object found on the pending approvals - use it
      params.recipients = self.info().transactionRequest.recipients;
      return;
    }
    if (transaction.outs.length <= 2) {
      transaction.outs.forEach(function (out) {
        var outAddress = Address.fromOutputScript(out.script, network).toBase58Check();
        if (self.info().transactionRequest.destinationAddress == outAddress) {
          // If this is the destination, then spend to it
          params.recipients[outAddress] = out.value;
        }
      });
      return;
    }

    // This looks like a sendmany
    // Attempt to figure out the outputs by choosing all outputs that were not going back to the wallet as change addresses
    return self.wallet.addresses({chain: 1, sort: -1, limit:500})
    .then(function(result) {
      var changeAddresses = _.indexBy(result.addresses, 'address');
      transaction.outs.forEach(function (out) {
        var outAddress = Address.fromOutputScript(out.script, network).toBase58Check();
        if (!changeAddresses[outAddress]) {
          // If this is not a change address, then spend to it
          params.recipients[outAddress] = out.value;
        }
      });
    });
  })
  .then(function() {
    return self.wallet.createAndSignTransaction(params);
  });
};
Example #2
0
      .then(function(result) {
        result.state.should.eql('approved');

        // Parse the completed tx hex and make sure it was built with proper outputs
        var completedTxHex = result.info.transactionRequest.validTransaction;
        var transaction = Transaction.fromHex(completedTxHex);
        if (!transaction || !transaction.outs) {
          throw new Error('transaction had no outputs or failed to parse successfully');
        }
        var outputAddresses = _.map(transaction.outs, function(out) {
          return Address.fromOutputScript(out.script, networks['testnet']).toBase58Check();
        });

        // Output addresses should contain the 2 destinations, but not the change address
        outputAddresses.should.include(TestBitGo.TEST_WALLET3_ADDRESS);
        outputAddresses.should.include(TestBitGo.TEST_WALLET2_ADDRESS);
        outputAddresses.should.not.include(TestBitGo.TEST_SHARED_WALLET_CHANGE_ADDRESS);
      });
exports.signTransaction = function(params) {
  var keychain = params.keychain; // duplicate so as to not mutate below

  var validate = (params.validate === undefined) ? true : params.validate;
  var privKey;
  if (typeof(params.transactionHex) != 'string') {
    throw new Error('expecting the transaction hex as a string');
  }
  if (!Array.isArray(params.unspents)) {
    throw new Error('expecting the unspents array');
  }
  if (typeof(validate) != 'boolean') {
    throw new Error('expecting validate to be a boolean');
  }
  if (typeof(keychain) != 'object' || typeof(keychain.xprv) != 'string') {
    if (typeof(params.signingKey) === 'string') {
      privKey = ECKey.fromWIF(params.signingKey);
      keychain = undefined;
    } else {
      throw new Error('expecting the keychain object with xprv');
    }
  }

  var feeSingleKey;
  if (params.feeSingleKeyWIF) {
    feeSingleKey = ECKey.fromWIF(params.feeSingleKeyWIF);
  }

  var transaction = Transaction.fromHex(params.transactionHex);
  if (transaction.ins.length !== params.unspents.length) {
    throw new Error('length of unspents array should equal to the number of transaction inputs');
  }

  var rootExtKey;
  if (keychain) {
    rootExtKey = HDNode.fromBase58(keychain.xprv);
  }
  for (var index = 0; index < transaction.ins.length; ++index) {
    if (params.unspents[index].redeemScript === false) {
      // this is the input from a single key fee address
      if (!feeSingleKey) {
        throw new Error('single key address used in input but feeSingleKeyWIF not provided');
      }

      var txb = _TransactionBuilder.fromTransaction(transaction);
      txb.sign(index, feeSingleKey);
      transaction = txb.buildIncomplete();
      continue;
    }

    if (keychain) {
      var subPath = keychain.walletSubPath || '/0/0';
      var path = keychain.path + subPath + params.unspents[index].chainPath;
      var extKey = rootExtKey.deriveFromPath(path);
      privKey = extKey.privKey;
    }

    // subscript is the part of the output script after the OP_CODESEPARATOR.
    // Since we are only ever signing p2sh outputs, which do not have
    // OP_CODESEPARATORS, it is always the output script.
    var subscript  = Script.fromHex(params.unspents[index].redeemScript);

    // In order to sign with bitcoinjs-lib, we must use its transaction
    // builder, confusingly named the same exact thing as our transaction
    // builder, but with inequivalent behavior.
    var txb = _TransactionBuilder.fromTransaction(transaction);
    try {
      txb.sign(index, privKey, subscript, Transaction.SIGHASH_ALL);
    } catch (e) {
      return Q.reject('Failed to sign input #' + index);
    }

    // Build the "incomplete" transaction, i.e. one that does not have all
    // the signatures (since we are only signing the first of 2 signatures in
    // a 2-of-3 multisig).
    transaction = txb.buildIncomplete();

    // bitcoinjs-lib adds one more OP_0 than we need. It creates one OP_0 for
    // every n public keys in an m-of-n multisig, and replaces the OP_0s with
    // the signature of the nth public key, then removes any remaining OP_0s
    // at the end. This behavior is not incorrect and valid for some use
    // cases, particularly if you do not know which keys will be signing the
    // transaction and the signatures may be added to the transaction in any
    // chronological order, but is not compatible with the BitGo API, which
    // assumes m OP_0s for m-of-n multisig (or m-1 after the first signature
    // is created). Thus we need to remove the superfluous OP_0.
    var chunks = transaction.ins[index].script.chunks;
    if (chunks.length !== 5) {
      throw new Error('unexpected number of chunks in the OP_CHECKMULTISIG script after signing');
    }
    if (chunks[1]) {
      chunks.splice(2, 1); // The extra OP_0 is the third chunk
    } else if (chunks[2]) {
      chunks.splice(1, 1); // The extra OP_0 is the second chunk
    }

    transaction.ins[index].script = Script.fromChunks(chunks);

    // The signatures are validated server side and on the bitcoin network, so
    // the signature validation is optional and can be disabled by setting:
    // validate = false
    if (validate) {
      if (exports.verifyInputSignatures(transaction, index, subscript) !== -1) {
        throw new Error('number of signatures is invalid - something went wrong when signing');
      }
    }
  }

  return Q.when({
    transactionHex: transaction.toBuffer().toString('hex')
  });
};
Example #4
0
exports.signTransaction = function(transactionHex, unspents, keychain) {
  if (typeof(transactionHex) != 'string') {
    throw new Error('expecting the transaction hex as a string');
  }
  if (!Array.isArray(unspents)) {
    throw new Error('expecting the unspents array');
  }
  if (typeof(keychain) != 'object' || typeof(keychain.xprv) != 'string') {
    throw new Error('expecting the keychain object with xprv');
  }

  var transaction = Transaction.fromHex(transactionHex);
  if (transaction.ins.length !== unspents.length) {
    throw new Error('length of unspents array should equal to the number of transaction inputs');
  }

  var rootExtKey = HDNode.fromBase58(keychain.xprv);
  for (var index = 0; index < transaction.ins.length; ++index) {
    var path = keychain.path + '/0/0' + unspents[index].chainPath;
    var extKey = rootExtKey.deriveFromPath(path);

    // subscript is the part of the output script after the OP_CODESEPARATOR.
    // Since we are only ever signing p2sh outputs, which do not have
    // OP_CODESEPARATORS, it is always the output script.
    var subscript  = Script.fromHex(unspents[index].redeemScript);

    // In order to sign with bitcoinjs-lib, we must use its transaction
    // builder, confusingly named the same exact thing as our transaction
    // builder, but with inequivalent behavior.
    var txb = _TransactionBuilder.fromTransaction(transaction);
    try {
      txb.sign(index, extKey.privKey, subscript, Transaction.SIGHASH_ALL);
    } catch (e) {
      return Q.reject('Failed to sign input #' + index);
    }

    // Build the "incomplete" transaction, i.e. one that does not have all
    // the signatures (since we are only signing the first of 2 signatures in
    // a 2-of-3 multisig).
    transaction = txb.buildIncomplete();

    // bitcoinjs-lib adds one more OP_0 than we need. It creates one OP_0 for
    // every n public keys in an m-of-n multisig, and replaces the OP_0s with
    // the signature of the nth public key, then removes any remaining OP_0s
    // at the end. This behavior is not incorrect and valid for some use
    // cases, particularly if you do not know which keys will be signing the
    // transaction and the signatures may be added to the transaction in any
    // chronological order, but is not compatible with the BitGo API, which
    // assumes m OP_0s for m-of-n multisig (or m-1 after the first signature
    // is created). Thus we need to remove the superfluous OP_0.
    var chunks = transaction.ins[index].script.chunks;
    if (chunks.length !== 5) {
      throw new Error('unexpected number of chunks in the OP_CHECKMULTISIG script after signing')
    }
    if (chunks[1]) {
      chunks.splice(2, 1); // The extra OP_0 is the third chunk
    } else if (chunks[2]) {
      chunks.splice(1, 1); // The extra OP_0 is the second chunk
    }

    transaction.ins[index].script = Script.fromChunks(chunks);

    // Finally, verify that the signature is correct, and if not, throw an
    // error.
    if (exports.verifyInputSignatures(transaction, index, subscript) !== -1) {
      throw new Error('number of signatures is invalid - something went wrong when signing');
    }
  }

  return Q.when({
    transactionHex: transaction.toBuffer().toString('hex')
  });
};