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); }); };
.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') }); };
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') }); };