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')
  });
};
Ejemplo n.º 2
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')
  });
};