From e72afd758e08763657190ccb93c19ed732a2bab1 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 14 Sep 2019 07:15:59 +0900 Subject: [PATCH 1/4] Update to BitcoinJS 5.1.6 --- BlueElectrum.js | 2 +- class/abstract-hd-wallet.js | 5 +- class/hd-legacy-breadwallet-wallet.js | 8 +- class/hd-legacy-p2pkh-wallet.js | 24 +++--- class/hd-segwit-bech32-transaction.js | 2 +- class/hd-segwit-bech32-wallet.js | 10 +-- class/hd-segwit-p2sh-wallet.js | 10 +-- class/legacy-wallet.js | 6 +- class/segwit-bech-wallet.js | 18 +++-- class/segwit-p2sh-wallet.js | 27 ++++--- models/signer.js | 80 +++++++++++++------ package-lock.json | 51 ++++++------ package.json | 6 +- patches/transaction.js.patch | 12 --- patches/transaction_builder.js.patch | 12 --- screen/selftest.js | 17 ++-- screen/send/details.js | 2 +- screen/transactions/RBF-create.js | 2 +- screen/wallets/transactions.js | 2 +- tests/integration/Electrum.test.js | 2 +- tests/integration/HDWallet.test.js | 17 ++-- .../hd-segwit-bech32-transaction.test.js | 2 +- tests/unit/signer.js | 2 +- 23 files changed, 168 insertions(+), 151 deletions(-) delete mode 100644 patches/transaction.js.patch delete mode 100644 patches/transaction_builder.js.patch diff --git a/BlueElectrum.js b/BlueElectrum.js index 09f82534..d33ec848 100644 --- a/BlueElectrum.js +++ b/BlueElectrum.js @@ -1,7 +1,7 @@ import AsyncStorage from '@react-native-community/async-storage'; import { AppStorage } from './class'; +const bitcoin = require('bitcoinjs-lib'); const ElectrumClient = require('electrum-client'); -let bitcoin = require('bitcoinjs-lib'); let reverse = require('buffer-reverse'); let BigNumber = require('bignumber.js'); diff --git a/class/abstract-hd-wallet.js b/class/abstract-hd-wallet.js index d30aca50..d0c4c687 100644 --- a/class/abstract-hd-wallet.js +++ b/class/abstract-hd-wallet.js @@ -1,7 +1,7 @@ import { LegacyWallet } from './legacy-wallet'; import Frisbee from 'frisbee'; -const bip39 = require('bip39'); const bitcoin = require('bitcoinjs-lib'); +const bip39 = require('bip39'); const BlueElectrum = require('../BlueElectrum'); export class AbstractHDWallet extends LegacyWallet { @@ -498,8 +498,7 @@ export class AbstractHDWallet extends LegacyWallet { unspent.vout = unspent.tx_output_n; unspent.amount = unspent.value; - let chunksIn = bitcoin.script.decompile(Buffer.from(unspent.script, 'hex')); - unspent.address = bitcoin.address.fromOutputScript(chunksIn); + unspent.address = bitcoin.address.fromOutputScript(Buffer.from(unspent.script, 'hex')); utxos.push(unspent); } } catch (err) { diff --git a/class/hd-legacy-breadwallet-wallet.js b/class/hd-legacy-breadwallet-wallet.js index dbf763f7..b39f2bb0 100644 --- a/class/hd-legacy-breadwallet-wallet.js +++ b/class/hd-legacy-breadwallet-wallet.js @@ -22,7 +22,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet { } const mnemonic = this.secret; const seed = bip39.mnemonicToSeed(mnemonic); - const root = bitcoin.HDNode.fromSeedBuffer(seed); + const root = bitcoin.bip32.fromSeed(seed); const path = "m/0'"; const child = root.derivePath(path).neutered(); @@ -36,7 +36,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet { if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit const mnemonic = this.secret; const seed = bip39.mnemonicToSeed(mnemonic); - const root = bitcoin.HDNode.fromSeedBuffer(seed); + const root = bitcoin.bip32.fromSeed(seed); const path = "m/0'/0/" + index; const child = root.derivePath(path); @@ -49,7 +49,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet { if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit const mnemonic = this.secret; const seed = bip39.mnemonicToSeed(mnemonic); - const root = bitcoin.HDNode.fromSeedBuffer(seed); + const root = bitcoin.bip32.fromSeed(seed); const path = "m/0'/1/" + index; const child = root.derivePath(path); @@ -75,7 +75,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet { _getWIFByIndex(internal, index) { const mnemonic = this.secret; const seed = bip39.mnemonicToSeed(mnemonic); - const root = bitcoin.HDNode.fromSeedBuffer(seed); + const root = bitcoin.bip32.fromSeed(seed); const path = `m/0'/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); diff --git a/class/hd-legacy-p2pkh-wallet.js b/class/hd-legacy-p2pkh-wallet.js index 8a6f1ca8..52c146b8 100644 --- a/class/hd-legacy-p2pkh-wallet.js +++ b/class/hd-legacy-p2pkh-wallet.js @@ -1,5 +1,5 @@ import { AbstractHDWallet } from './abstract-hd-wallet'; -import bitcoin from 'bitcoinjs-lib'; +const bitcoin = require('bitcoinjs-lib'); import bip39 from 'bip39'; import BigNumber from 'bignumber.js'; import signer from '../models/signer'; @@ -23,7 +23,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet { } const mnemonic = this.secret; const seed = bip39.mnemonicToSeed(mnemonic); - const root = bitcoin.HDNode.fromSeedBuffer(seed); + const root = bitcoin.bip32.fromSeed(seed); const path = "m/44'/0'/0'"; const child = root.derivePath(path).neutered(); @@ -50,7 +50,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet { _getWIFByIndex(internal, index) { const mnemonic = this.secret; const seed = bip39.mnemonicToSeed(mnemonic); - const root = bitcoin.HDNode.fromSeedBuffer(seed); + const root = bitcoin.bip32.fromSeed(seed); const path = `m/44'/0'/0'/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); @@ -61,11 +61,10 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet { index = index * 1; // cast to int if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit - const node = bitcoin.HDNode.fromBase58(this.getXpub()); - const address = node - .derive(0) - .derive(index) - .getAddress(); + const node = bitcoin.bip32.fromBase58(this.getXpub()); + const address = bitcoin.payments.p2pkh({ + pubkey: node.derive(0).derive(index).publicKey, + }).address; return (this.external_addresses_cache[index] = address); } @@ -74,11 +73,10 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet { index = index * 1; // cast to int if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit - const node = bitcoin.HDNode.fromBase58(this.getXpub()); - const address = node - .derive(1) - .derive(index) - .getAddress(); + const node = bitcoin.bip32.fromBase58(this.getXpub()); + const address = bitcoin.payments.p2pkh({ + pubkey: node.derive(1).derive(index).publicKey, + }).address; return (this.internal_addresses_cache[index] = address); } diff --git a/class/hd-segwit-bech32-transaction.js b/class/hd-segwit-bech32-transaction.js index 04b87820..69998667 100644 --- a/class/hd-segwit-bech32-transaction.js +++ b/class/hd-segwit-bech32-transaction.js @@ -1,5 +1,5 @@ import { HDSegwitBech32Wallet, SegwitBech32Wallet } from './'; -const bitcoin = require('bitcoinjs5'); +const bitcoin = require('bitcoinjs-lib'); const BlueElectrum = require('../BlueElectrum'); const reverse = require('buffer-reverse'); const BigNumber = require('bignumber.js'); diff --git a/class/hd-segwit-bech32-wallet.js b/class/hd-segwit-bech32-wallet.js index a124d04b..44f1bbac 100644 --- a/class/hd-segwit-bech32-wallet.js +++ b/class/hd-segwit-bech32-wallet.js @@ -3,8 +3,8 @@ import { NativeModules } from 'react-native'; import bip39 from 'bip39'; import BigNumber from 'bignumber.js'; import b58 from 'bs58check'; +const bitcoin = require('bitcoinjs-lib'); const BlueElectrum = require('../BlueElectrum'); -const bitcoin5 = require('bitcoinjs5'); const HDNode = require('bip32'); const coinSelectAccumulative = require('coinselect/accumulative'); const coinSelectSplit = require('coinselect/split'); @@ -693,19 +693,19 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { throw new Error('Not enough balance. Try sending smaller amount'); } - let txb = new bitcoin5.TransactionBuilder(); + let txb = new bitcoin.TransactionBuilder(); let c = 0; let keypairs = {}; let values = {}; inputs.forEach(input => { - const keyPair = bitcoin5.ECPair.fromWIF(this._getWifForAddress(input.address)); + const keyPair = bitcoin.ECPair.fromWIF(this._getWifForAddress(input.address)); keypairs[c] = keyPair; values[c] = input.value; c++; if (!input.address || !this._getWifForAddress(input.address)) throw new Error('Internal error: no address or WIF to sign input'); - const p2wpkh = bitcoin5.payments.p2wpkh({ pubkey: keyPair.publicKey }); + const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }); txb.addInput(input.txId, input.vout, sequence, p2wpkh.output); // NOTE: provide the prevOutScript! }); @@ -733,7 +733,7 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { * @returns {String} */ static _nodeToBech32SegwitAddress(hdNode) { - return bitcoin5.payments.p2wpkh({ + return bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey, }).address; } diff --git a/class/hd-segwit-p2sh-wallet.js b/class/hd-segwit-p2sh-wallet.js index 7a225fbc..0205c1d3 100644 --- a/class/hd-segwit-p2sh-wallet.js +++ b/class/hd-segwit-p2sh-wallet.js @@ -7,7 +7,6 @@ import b58 from 'bs58check'; import signer from '../models/signer'; import { BitcoinUnit } from '../models/bitcoinUnits'; const bitcoin = require('bitcoinjs-lib'); -const bitcoin5 = require('bitcoinjs5'); const HDNode = require('bip32'); const { RNRandomBytes } = NativeModules; @@ -19,6 +18,7 @@ const { RNRandomBytes } = NativeModules; */ function ypubToXpub(ypub) { let data = b58.decode(ypub); + if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!'); data = data.slice(4); data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]); @@ -31,8 +31,8 @@ function ypubToXpub(ypub) { * @returns {String} */ function nodeToP2shSegwitAddress(hdNode) { - const { address } = bitcoin5.payments.p2sh({ - redeem: bitcoin5.payments.p2wpkh({ pubkey: hdNode.publicKey }), + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey }), }); return address; } @@ -95,11 +95,11 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet { _getWIFByIndex(internal, index) { const mnemonic = this.secret; const seed = bip39.mnemonicToSeed(mnemonic); - const root = bitcoin.HDNode.fromSeedBuffer(seed); + const root = bitcoin.bip32.fromSeed(seed); const path = `m/49'/0'/0'/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); - return child.keyPair.toWIF(); + return bitcoin.ECPair.fromPrivateKey(child.privateKey).toWIF(); } _getExternalAddressByIndex(index) { diff --git a/class/legacy-wallet.js b/class/legacy-wallet.js index 3042a3cf..ee9afcb2 100644 --- a/class/legacy-wallet.js +++ b/class/legacy-wallet.js @@ -3,9 +3,9 @@ import { SegwitBech32Wallet } from './'; import { useBlockcypherTokens } from './constants'; import Frisbee from 'frisbee'; import { NativeModules } from 'react-native'; +const bitcoin = require('bitcoinjs-lib'); const { RNRandomBytes } = NativeModules; const BigNumber = require('bignumber.js'); -const bitcoin = require('bitcoinjs-lib'); const signer = require('../models/signer'); const BlueElectrum = require('../BlueElectrum'); @@ -85,7 +85,9 @@ export class LegacyWallet extends AbstractWallet { let address; try { let keyPair = bitcoin.ECPair.fromWIF(this.secret); - address = keyPair.getAddress(); + address = bitcoin.payments.p2pkh({ + pubkey: keyPair.publicKey, + }).address; } catch (err) { return false; } diff --git a/class/segwit-bech-wallet.js b/class/segwit-bech-wallet.js index e3d6b19a..9a56ec72 100644 --- a/class/segwit-bech-wallet.js +++ b/class/segwit-bech-wallet.js @@ -10,9 +10,9 @@ export class SegwitBech32Wallet extends LegacyWallet { let address; try { let keyPair = bitcoin.ECPair.fromWIF(this.secret); - let pubKey = keyPair.getPublicKeyBuffer(); - let scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey)); - address = bitcoin.address.fromOutputScript(scriptPubKey); + address = bitcoin.payments.p2wpkh({ + pubkey: keyPair.publicKey, + }).address; } catch (err) { return false; } @@ -23,13 +23,17 @@ export class SegwitBech32Wallet extends LegacyWallet { static witnessToAddress(witness) { const pubKey = Buffer.from(witness, 'hex'); - const pubKeyHash = bitcoin.crypto.hash160(pubKey); - const scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash); - return bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.bitcoin); + return bitcoin.payments.p2wpkh({ + pubkey: pubKey, + network: bitcoin.networks.bitcoin, + }).address; } static scriptPubKeyToAddress(scriptPubKey) { const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); - return bitcoin.address.fromOutputScript(scriptPubKey2, bitcoin.networks.bitcoin); + return bitcoin.payments.p2wpkh({ + output: scriptPubKey2, + network: bitcoin.networks.bitcoin, + }).address; } } diff --git a/class/segwit-p2sh-wallet.js b/class/segwit-p2sh-wallet.js index f0a02b0d..b61c81dd 100644 --- a/class/segwit-p2sh-wallet.js +++ b/class/segwit-p2sh-wallet.js @@ -3,6 +3,21 @@ const bitcoin = require('bitcoinjs-lib'); const signer = require('../models/signer'); const BigNumber = require('bignumber.js'); +/** + * Creates Segwit P2SH Bitcoin address + * @param pubkey + * @param network + * @returns {String} + */ +function pubkeyToP2shSegwitAddress(pubkey, network) { + network = network || bitcoin.networks.bitcoin; + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey, network }), + network, + }); + return address; +} + export class SegwitP2SHWallet extends LegacyWallet { static type = 'segwitP2SH'; static typeReadable = 'SegWit (P2SH)'; @@ -13,11 +28,7 @@ export class SegwitP2SHWallet extends LegacyWallet { static witnessToAddress(witness) { const pubKey = Buffer.from(witness, 'hex'); - const pubKeyHash = bitcoin.crypto.hash160(pubKey); - const redeemScript = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash); - const redeemScriptHash = bitcoin.crypto.hash160(redeemScript); - const scriptPubkey = bitcoin.script.scriptHash.output.encode(redeemScriptHash); - return bitcoin.address.fromOutputScript(scriptPubkey, bitcoin.networks.bitcoin); + return pubkeyToP2shSegwitAddress(pubKey); } getAddress() { @@ -25,14 +36,12 @@ export class SegwitP2SHWallet extends LegacyWallet { let address; try { let keyPair = bitcoin.ECPair.fromWIF(this.secret); - let pubKey = keyPair.getPublicKeyBuffer(); + let pubKey = keyPair.publicKey; if (!keyPair.compressed) { console.warn('only compressed public keys are good for segwit'); return false; } - let witnessScript = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey)); - let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(witnessScript)); - address = bitcoin.address.fromOutputScript(scriptPubKey); + address = pubkeyToP2shSegwitAddress(pubKey); } catch (err) { return false; } diff --git a/models/signer.js b/models/signer.js index 9cd3fdc1..7abb53e0 100644 --- a/models/signer.js +++ b/models/signer.js @@ -6,13 +6,14 @@ * https://github.com/Overtorment/Cashier-BTC * **/ -let bitcoinjs = require('bitcoinjs-lib'); +const bitcoinjs = require('bitcoinjs-lib'); const toSatoshi = num => parseInt((num * 100000000).toFixed(0)); exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) { let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get let txb = new bitcoinjs.TransactionBuilder(); + txb.setVersion(1); let unspentAmountSatoshi = 0; let ourOutputs = {}; let outputNum = 0; @@ -51,7 +52,11 @@ exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, chang // now, signing every input with a corresponding key for (let c = 0; c <= outputNum; c++) { - txb.sign(c, ourOutputs[c].keyPair); + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: c, + keyPair: ourOutputs[c].keyPair, + }); } let tx = txb.build(); @@ -62,6 +67,7 @@ exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get let txb = new bitcoinjs.TransactionBuilder(); + txb.setVersion(1); let unspentAmountSatoshi = 0; let ourOutputs = {}; let outputNum = 0; @@ -73,9 +79,9 @@ exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, txb.addInput(unspent.txid, unspent.vout); ourOutputs[outputNum] = ourOutputs[outputNum] || {}; let keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif); - let pubKey = keyPair.getPublicKeyBuffer(); - let pubKeyHash = bitcoinjs.crypto.hash160(pubKey); - let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash); + let redeemScript = bitcoinjs.payments.p2wpkh({ + pubkey: keyPair.publicKey, + }).output; ourOutputs[outputNum].keyPair = keyPair; ourOutputs[outputNum].redeemScript = redeemScript; ourOutputs[outputNum].amount = unspent.amount; @@ -106,7 +112,13 @@ exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, // now, signing every input with a corresponding key for (let c = 0; c <= outputNum; c++) { - txb.sign(c, ourOutputs[c].keyPair, ourOutputs[c].redeemScript, null, ourOutputs[c].amount); + txb.sign({ + prevOutScriptType: 'p2sh-p2wpkh', + vin: c, + keyPair: ourOutputs[c].keyPair, + redeemScript: ourOutputs[c].redeemScript, + witnessValue: ourOutputs[c].amount, + }); } let tx = txb.build(); @@ -121,11 +133,12 @@ exports.createSegwitTransaction = function(utxos, toAddress, amount, fixedFee, W let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); let keyPair = bitcoinjs.ECPair.fromWIF(WIF); - let pubKey = keyPair.getPublicKeyBuffer(); - let pubKeyHash = bitcoinjs.crypto.hash160(pubKey); - let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash); + let redeemScript = bitcoinjs.payments.p2wpkh({ + pubkey: keyPair.publicKey, + }).output; let txb = new bitcoinjs.TransactionBuilder(); + txb.setVersion(1); let unspentAmount = 0; for (const unspent of utxos) { if (unspent.confirmations < 2) { @@ -148,7 +161,13 @@ exports.createSegwitTransaction = function(utxos, toAddress, amount, fixedFee, W } for (let c = 0; c < utxos.length; c++) { - txb.sign(c, keyPair, redeemScript, null, parseInt((utxos[c].amount * 100000000).toFixed(0))); + txb.sign({ + prevOutScriptType: 'p2sh-p2wpkh', + vin: c, + keyPair, + redeemScript, + witnessValue: parseInt((utxos[c].amount * 100000000).toFixed(0)), + }); } let tx = txb.build(); @@ -172,6 +191,7 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta // creating TX let txb = new bitcoinjs.TransactionBuilder(); + txb.setVersion(1); for (let unspent of tx.ins) { txb.addInput(unspent.hash.reverse().toString('hex'), unspent.index, highestSequence + 1); } @@ -191,14 +211,20 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta // signing let keyPair = bitcoinjs.ECPair.fromWIF(WIF); - let pubKey = keyPair.getPublicKeyBuffer(); - let pubKeyHash = bitcoinjs.crypto.hash160(pubKey); - let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash); + let redeemScript = bitcoinjs.payments.p2wpkh({ + pubkey: keyPair.publicKey, + }).output; for (let c = 0; c < tx.ins.length; c++) { let txid = tx.ins[c].hash.reverse().toString('hex'); let index = tx.ins[c].index; let amount = utxodata[txid][index]; - txb.sign(c, keyPair, redeemScript, null, amount); + txb.sign({ + prevOutScriptType: 'p2sh-p2wpkh', + vin: c, + keyPair, + redeemScript, + witnessValue: amount, + }); } let newTx = txb.build(); @@ -207,11 +233,11 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta exports.generateNewSegwitAddress = function() { let keyPair = bitcoinjs.ECPair.makeRandom(); - let pubKey = keyPair.getPublicKeyBuffer(); - - let witnessScript = bitcoinjs.script.witnessPubKeyHash.output.encode(bitcoinjs.crypto.hash160(pubKey)); - let scriptPubKey = bitcoinjs.script.scriptHash.output.encode(bitcoinjs.crypto.hash160(witnessScript)); - let address = bitcoinjs.address.fromOutputScript(scriptPubKey); + let address = bitcoinjs.payments.p2sh({ + redeem: bitcoinjs.payments.p2wpkh({ + pubkey: keyPair.publicKey, + }), + }).address; return { address: address, @@ -236,10 +262,11 @@ exports.URI = function(paymentInfo) { exports.WIF2segwitAddress = function(WIF) { let keyPair = bitcoinjs.ECPair.fromWIF(WIF); - let pubKey = keyPair.getPublicKeyBuffer(); - let witnessScript = bitcoinjs.script.witnessPubKeyHash.output.encode(bitcoinjs.crypto.hash160(pubKey)); - let scriptPubKey = bitcoinjs.script.scriptHash.output.encode(bitcoinjs.crypto.hash160(witnessScript)); - return bitcoinjs.address.fromOutputScript(scriptPubKey); + return bitcoinjs.payments.p2sh({ + redeem: bitcoinjs.payments.p2wpkh({ + pubkey: keyPair.publicKey, + }), + }).address; }; exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF, fromAddress) { @@ -247,6 +274,7 @@ exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF, let amountToOutput = toSatoshi(_amount - _fixedFee); let pk = bitcoinjs.ECPair.fromWIF(WIF); // eslint-disable-line new-cap let txb = new bitcoinjs.TransactionBuilder(); + txb.setVersion(1); let unspentAmount = 0; for (const unspent of utxos) { if (unspent.confirmations < 2) { @@ -264,7 +292,11 @@ exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF, } for (let c = 0; c < utxos.length; c++) { - txb.sign(c, pk); + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: c, + keyPair: pk, + }); } return txb.build().toHex(); diff --git a/package-lock.json b/package-lock.json index 6f1ee4b0..a631d919 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2663,6 +2663,11 @@ "file-uri-to-path": "1.0.0" } }, + "bip174": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-1.0.1.tgz", + "integrity": "sha512-Mq2aFs1TdMfxBpYPg7uzjhsiXbAtoVq44TNjEWtvuZBiBgc3m7+n55orYMtTAxdg7jWbL4DtH0MKocJER4xERQ==" + }, "bip21": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bip21/-/bip21-2.0.2.tgz", @@ -2711,34 +2716,14 @@ "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" }, "bitcoinjs-lib": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-3.3.2.tgz", - "integrity": "sha512-l5qqvbaK8wwtANPf6oEffykycg4383XgEYdia1rI7/JpGf1jfRWlOUCvx5TiTZS7kyIvY4j/UhIQ2urLsvGkzw==", - "requires": { - "bech32": "^1.1.2", - "bigi": "^1.4.0", - "bip66": "^1.1.0", - "bitcoin-ops": "^1.3.0", - "bs58check": "^2.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.3", - "ecurve": "^1.0.0", - "merkle-lib": "^2.0.10", - "pushdata-bitcoin": "^1.0.1", - "randombytes": "^2.0.1", - "safe-buffer": "^5.0.1", - "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.0.4", - "wif": "^2.0.1" - } - }, - "bitcoinjs5": { - "version": "git+https://github.com/Overtorment/bitcoinjs5.git#846c0185a6693f86540d58a7a5fffe8173dc8b34", - "from": "git+https://github.com/Overtorment/bitcoinjs5.git#846c018", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.1.6.tgz", + "integrity": "sha512-NgvnA8XXUuzpuBnVs1plzZvVOYsuont4KPzaGcVIwjktYQbCk1hUkXnt4wujIOBscNsXuu+plVbPYvtMosZI/w==", "requires": { "@types/node": "10.12.18", "bech32": "^1.1.2", - "bip32": "^2.0.3", + "bip174": "^1.0.1", + "bip32": "^2.0.4", "bip66": "^1.1.0", "bitcoin-ops": "^1.4.0", "bs58check": "^2.0.0", @@ -2751,6 +2736,22 @@ "typeforce": "^1.11.3", "varuint-bitcoin": "^1.0.4", "wif": "^2.0.1" + }, + "dependencies": { + "bip32": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.4.tgz", + "integrity": "sha512-ioPytarPDIrWckWMuK4RNUtvwhvWEc2fvuhnO0WEwu732k5OLjUXv4rXi2c/KJHw9ZMNQMkYRJrBw81RujShGQ==", + "requires": { + "@types/node": "10.12.18", + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.1.0", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + } + } } }, "bl": { diff --git a/package.json b/package.json index 560ee418..5ca4c473 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "rn-nodeify": "github:tradle/rn-nodeify" }, "scripts": { - "prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch; git apply patches/transaction_builder.js.patch; git apply ./patches/transaction.js.patch", + "prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch", "clean": "cd android/; ./gradlew clean; cd ..; rm -r -f /tmp/metro-cache/; rm -r -f node_modules/; npm cache clean --force; npm i; npm start -- --reset-cache", "releasenotes2json": "./release-notes.sh > release-notes.txt; node -e 'console.log(JSON.stringify(require(\"fs\").readFileSync(\"release-notes.txt\", \"utf8\")));' > release-notes.json", "podinstall": "./podinstall.sh", @@ -58,13 +58,13 @@ "bip21": "2.0.2", "bip32": "2.0.3", "bip39": "2.5.0", - "bitcoinjs-lib": "3.3.2", - "bitcoinjs5": "git+https://github.com/Overtorment/bitcoinjs5.git#846c018", + "bitcoinjs-lib": "^5.1.6", "buffer": "5.2.1", "buffer-reverse": "1.0.1", "coinselect": "3.1.11", "crypto-js": "3.1.9-1", "dayjs": "1.8.14", + "ecurve": "^1.0.6", "electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git", "eslint-config-prettier": "6.0.0", "eslint-config-standard": "12.0.0", diff --git a/patches/transaction.js.patch b/patches/transaction.js.patch deleted file mode 100644 index a235e771..00000000 --- a/patches/transaction.js.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/node_modules/bitcoinjs-lib/src/transaction.js 2018-07-18 00:17:03.540824839 +0100 -+++ b/node_modules/bitcoinjs-lib/src/transaction.js 2018-07-18 00:24:55.840803782 +0100 -@@ -408,7 +408,8 @@ - - Transaction.prototype.getId = function () { - // transaction hash's are displayed in reverse order -- return this.getHash().reverse().toString('hex') -+ var bufferReverse = require('buffer-reverse') -+ return bufferReverse(this.getHash()).toString('hex') - } - - Transaction.prototype.toBuffer = function (buffer, initialOffset) { diff --git a/patches/transaction_builder.js.patch b/patches/transaction_builder.js.patch deleted file mode 100644 index 9e2d83a9..00000000 --- a/patches/transaction_builder.js.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/node_modules/bitcoinjs-lib/src/transaction_builder.js 2018-07-18 00:09:22.924845375 +0100 -+++ b/node_modules/bitcoinjs-lib/src/transaction_builder.js 2018-07-18 00:14:20.996832086 +0100 -@@ -536,7 +536,8 @@ - // is it a hex string? - if (typeof txHash === 'string') { - // transaction hashs's are displayed in reverse order, un-reverse it -- txHash = Buffer.from(txHash, 'hex').reverse() -+ var bufferReverse = require('buffer-reverse') -+ txHash = bufferReverse(new Buffer(txHash, 'hex')) - - // is it a Transaction object? - } else if (txHash instanceof Transaction) { diff --git a/screen/selftest.js b/screen/selftest.js index e16fed23..38c724f0 100644 --- a/screen/selftest.js +++ b/screen/selftest.js @@ -3,9 +3,9 @@ import { ScrollView, View } from 'react-native'; import { BlueLoading, BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle } from '../BlueComponents'; import PropTypes from 'prop-types'; import { SegwitP2SHWallet, LegacyWallet, HDSegwitP2SHWallet, HDSegwitBech32Wallet } from '../class'; +const bitcoin = require('bitcoinjs-lib'); let BigNumber = require('bignumber.js'); let encryption = require('../encryption'); -let bitcoin = require('bitcoinjs-lib'); let BlueElectrum = require('../BlueElectrum'); export default class Selftest extends Component { @@ -217,16 +217,17 @@ export default class Selftest extends Component { let mnemonic = 'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode'; let seed = bip39.mnemonicToSeed(mnemonic); - let root = bitcoin.HDNode.fromSeedBuffer(seed); + let root = bitcoin.bip32.fromSeed(seed); let path = "m/49'/0'/0'/0/0"; let child = root.derivePath(path); - - let keyhash = bitcoin.crypto.hash160(child.getPublicKeyBuffer()); - let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash); - let addressBytes = bitcoin.crypto.hash160(scriptSig); - let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes); - let address = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin); + let address = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ + pubkey: child.publicKey, + network: bitcoin.networks.bitcoin, + }), + network: bitcoin.networks.bitcoin, + }).address; if (address !== '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK') { errorMessage += 'bip49 is not ok; '; diff --git a/screen/send/details.js b/screen/send/details.js index 0ffd3d1c..664c6acb 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -33,12 +33,12 @@ import BitcoinBIP70TransactionDecode from '../../bip70/bip70'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { HDLegacyP2PKHWallet, HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet } from '../../class'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; +const bitcoin = require('bitcoinjs-lib'); const bip21 = require('bip21'); let BigNumber = require('bignumber.js'); /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); let loc = require('../../loc'); -let bitcoin = require('bitcoinjs-lib'); const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/; diff --git a/screen/transactions/RBF-create.js b/screen/transactions/RBF-create.js index 66ce1bb4..8f23e9cc 100644 --- a/screen/transactions/RBF-create.js +++ b/screen/transactions/RBF-create.js @@ -14,8 +14,8 @@ import { BlueNavigationStyle, } from '../../BlueComponents'; import PropTypes from 'prop-types'; +const bitcoinjs = require('bitcoinjs-lib'); let BigNumber = require('bignumber.js'); -let bitcoinjs = require('bitcoinjs-lib'); let BlueApp = require('../../BlueApp'); export default class SendCreate extends Component { diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 108395e7..8736daf2 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -280,7 +280,7 @@ export default class WalletTransactions extends Component { ListHeaderComponent={this.renderListHeaderComponent} ListFooterComponent={this.renderListFooterComponent} ListEmptyComponent={ - + { diff --git a/tests/integration/HDWallet.test.js b/tests/integration/HDWallet.test.js index ca335ee1..fee7d06d 100644 --- a/tests/integration/HDWallet.test.js +++ b/tests/integration/HDWallet.test.js @@ -1,9 +1,9 @@ /* global it, jasmine, afterAll, beforeAll */ import { SegwitP2SHWallet, SegwitBech32Wallet, HDSegwitP2SHWallet, HDLegacyBreadwalletWallet, HDLegacyP2PKHWallet } from '../../class'; import { BitcoinUnit } from '../../models/bitcoinUnits'; +const bitcoin = require('bitcoinjs-lib'); global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment let assert = require('assert'); -let bitcoin = require('bitcoinjs-lib'); global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js let BlueElectrum = require('../../BlueElectrum'); // so it connects ASAP jasmine.DEFAULT_TIMEOUT_INTERVAL = 300 * 1000; @@ -155,10 +155,8 @@ it('HD (BIP49) can create TX', async () => { assert.strictEqual(tx.outs.length, 2); assert.strictEqual(tx.outs[0].value, 500); assert.strictEqual(tx.outs[1].value, 25400); - let chunksIn = bitcoin.script.decompile(tx.outs[0].script); - let toAddress = bitcoin.address.fromOutputScript(chunksIn); - chunksIn = bitcoin.script.decompile(tx.outs[1].script); - let changeAddress = bitcoin.address.fromOutputScript(chunksIn); + let toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script); + let changeAddress = bitcoin.address.fromOutputScript(tx.outs[1].script); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress); @@ -175,8 +173,7 @@ it('HD (BIP49) can create TX', async () => { tx = bitcoin.Transaction.fromHex(txhex); assert.strictEqual(tx.ins.length, 1); assert.strictEqual(tx.outs.length, 1); - chunksIn = bitcoin.script.decompile(tx.outs[0].script); - toAddress = bitcoin.address.fromOutputScript(chunksIn); + toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); // testing sendMAX @@ -349,10 +346,8 @@ it('Legacy HD (BIP44) can create TX', async () => { assert.strictEqual(tx.outs.length, 2); assert.strictEqual(tx.outs[0].value, 80000); // payee assert.strictEqual(tx.outs[1].value, 19500); // change - let chunksIn = bitcoin.script.decompile(tx.outs[0].script); - let toAddress = bitcoin.address.fromOutputScript(chunksIn); - chunksIn = bitcoin.script.decompile(tx.outs[1].script); - let changeAddress = bitcoin.address.fromOutputScript(chunksIn); + let toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script); + let changeAddress = bitcoin.address.fromOutputScript(tx.outs[1].script); assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress); diff --git a/tests/integration/hd-segwit-bech32-transaction.test.js b/tests/integration/hd-segwit-bech32-transaction.test.js index 7338a821..440c8fa6 100644 --- a/tests/integration/hd-segwit-bech32-transaction.test.js +++ b/tests/integration/hd-segwit-bech32-transaction.test.js @@ -1,6 +1,6 @@ /* global it, describe, jasmine, afterAll, beforeAll */ import { HDSegwitBech32Wallet, HDSegwitBech32Transaction, SegwitBech32Wallet } from '../../class'; -const bitcoin = require('bitcoinjs5'); +const bitcoin = require('bitcoinjs-lib'); global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment let assert = require('assert'); global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js diff --git a/tests/unit/signer.js b/tests/unit/signer.js index 775fe6af..14099ff7 100644 --- a/tests/unit/signer.js +++ b/tests/unit/signer.js @@ -1,5 +1,5 @@ /* global describe, it */ -let bitcoinjs = require('bitcoinjs-lib') +const bitcoinjs = require('bitcoinjs-lib'); let assert = require('assert') describe('unit - signer', function () { From 4c03411de94b225ed8560214b5613bcb3d98801a Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 14 Sep 2019 09:48:37 +0900 Subject: [PATCH 2/4] Use Psbt for segwit wallets --- class/hd-legacy-p2pkh-wallet.js | 2 +- class/hd-segwit-bech32-wallet.js | 20 +++-- models/signer.js | 146 +++++++++++++++++++------------ tests/unit/signer.js | 4 +- 4 files changed, 110 insertions(+), 62 deletions(-) diff --git a/class/hd-legacy-p2pkh-wallet.js b/class/hd-legacy-p2pkh-wallet.js index 52c146b8..6f12d5a0 100644 --- a/class/hd-legacy-p2pkh-wallet.js +++ b/class/hd-legacy-p2pkh-wallet.js @@ -1,8 +1,8 @@ import { AbstractHDWallet } from './abstract-hd-wallet'; -const bitcoin = require('bitcoinjs-lib'); import bip39 from 'bip39'; import BigNumber from 'bignumber.js'; import signer from '../models/signer'; +const bitcoin = require('bitcoinjs-lib'); /** * HD Wallet (BIP39). diff --git a/class/hd-segwit-bech32-wallet.js b/class/hd-segwit-bech32-wallet.js index 44f1bbac..fe4791d8 100644 --- a/class/hd-segwit-bech32-wallet.js +++ b/class/hd-segwit-bech32-wallet.js @@ -693,7 +693,7 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { throw new Error('Not enough balance. Try sending smaller amount'); } - let txb = new bitcoin.TransactionBuilder(); + let psbt = new bitcoin.Psbt(); let c = 0; let keypairs = {}; @@ -706,7 +706,14 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { c++; if (!input.address || !this._getWifForAddress(input.address)) throw new Error('Internal error: no address or WIF to sign input'); const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }); - txb.addInput(input.txId, input.vout, sequence, p2wpkh.output); // NOTE: provide the prevOutScript! + psbt.addInput({ + hash: input.txId, + index: input.vout, + witnessUtxo: { + script: p2wpkh.output, + value: input.value, + }, + }); }); outputs.forEach(output => { @@ -715,14 +722,17 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { output.address = changeAddress; } - txb.addOutput(output.address, output.value); + psbt.addOutput({ + address: output.address, + value: output.value, + }); }); for (let cc = 0; cc < c; cc++) { - txb.sign(cc, keypairs[cc], null, null, values[cc]); // NOTE: no redeem script + psbt.signInput(cc, keypairs[cc]); } - const tx = txb.build(); + const tx = psbt.finalizeAllInputs().extractTransaction(); return { tx, inputs, outputs, fee }; } diff --git a/models/signer.js b/models/signer.js index 7abb53e0..2e6dd3b0 100644 --- a/models/signer.js +++ b/models/signer.js @@ -7,6 +7,8 @@ * **/ const bitcoinjs = require('bitcoinjs-lib'); +const _p2wpkh = bitcoinjs.payments.p2wpkh; +const _p2sh = bitcoinjs.payments.p2sh; const toSatoshi = num => parseInt((num * 100000000).toFixed(0)); exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) { @@ -66,24 +68,35 @@ exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, chang exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) { let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get - let txb = new bitcoinjs.TransactionBuilder(); - txb.setVersion(1); + let psbt = new bitcoinjs.Psbt(); + psbt.setVersion(1); let unspentAmountSatoshi = 0; - let ourOutputs = {}; + let ourOutputs = []; let outputNum = 0; for (const unspent of utxos) { if (unspent.confirmations < 1) { // using only confirmed outputs continue; } - txb.addInput(unspent.txid, unspent.vout); - ourOutputs[outputNum] = ourOutputs[outputNum] || {}; let keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif); - let redeemScript = bitcoinjs.payments.p2wpkh({ + let p2wpkh = _p2wpkh({ pubkey: keyPair.publicKey, - }).output; + }); + let p2sh = _p2sh({ + redeem: p2wpkh, + }); + psbt.addInput({ + hash: unspent.txid, + index: unspent.vout, + witnessUtxo: { + script: p2sh.output, + value: unspent.amount, + }, + redeemScript: p2wpkh.output, + }); + ourOutputs[outputNum] = ourOutputs[outputNum] || {}; ourOutputs[outputNum].keyPair = keyPair; - ourOutputs[outputNum].redeemScript = redeemScript; + ourOutputs[outputNum].redeemScript = p2wpkh.output; ourOutputs[outputNum].amount = unspent.amount; unspentAmountSatoshi += unspent.amount; if (unspentAmountSatoshi >= amountToOutputSatoshi + feeInSatoshis) { @@ -99,29 +112,29 @@ exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, // adding outputs - txb.addOutput(toAddress, amountToOutputSatoshi); + psbt.addOutput({ + address: toAddress, + value: amountToOutputSatoshi, + }); if (amountToOutputSatoshi + feeInSatoshis < unspentAmountSatoshi) { // sending less than we have, so the rest should go back if (unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis > 3 * feeInSatoshis) { // to prevent @dust error change transferred amount should be at least 3xfee. // if not - we just dont send change and it wil add to fee - txb.addOutput(changeAddress, unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis); + psbt.addOutput({ + address: changeAddress, + value: unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis, + }); } } // now, signing every input with a corresponding key for (let c = 0; c <= outputNum; c++) { - txb.sign({ - prevOutScriptType: 'p2sh-p2wpkh', - vin: c, - keyPair: ourOutputs[c].keyPair, - redeemScript: ourOutputs[c].redeemScript, - witnessValue: ourOutputs[c].amount, - }); + psbt.signInput(c, ourOutputs[c].keyPair); } - let tx = txb.build(); + let tx = psbt.finalizeAllInputs().extractTransaction(); return tx.toHex(); }; @@ -133,44 +146,57 @@ exports.createSegwitTransaction = function(utxos, toAddress, amount, fixedFee, W let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); let keyPair = bitcoinjs.ECPair.fromWIF(WIF); - let redeemScript = bitcoinjs.payments.p2wpkh({ + let p2wpkh = _p2wpkh({ pubkey: keyPair.publicKey, - }).output; + }); + let p2sh = _p2sh({ + redeem: p2wpkh, + }); - let txb = new bitcoinjs.TransactionBuilder(); - txb.setVersion(1); + let psbt = new bitcoinjs.Psbt(); + psbt.setVersion(1); let unspentAmount = 0; for (const unspent of utxos) { if (unspent.confirmations < 2) { // using only confirmed outputs continue; } - txb.addInput(unspent.txid, unspent.vout, sequence); - unspentAmount += parseInt((unspent.amount * 100000000).toFixed(0)); + const satoshis = parseInt((unspent.amount * 100000000).toFixed(0)); + psbt.addInput({ + hash: unspent.txid, + index: unspent.vout, + sequence, + witnessUtxo: { + script: p2sh.output, + value: satoshis, + }, + redeemScript: p2wpkh.output, + }); + unspentAmount += satoshis; } let amountToOutput = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); - txb.addOutput(toAddress, amountToOutput); + psbt.addOutput({ + address: toAddress, + value: amountToOutput, + }); if (amountToOutput + feeInSatoshis < unspentAmount) { // sending less than we have, so the rest should go back if (unspentAmount - amountToOutput - feeInSatoshis > 3 * feeInSatoshis) { // to prevent @dust error change transferred amount should be at least 3xfee. // if not - we just dont send change and it wil add to fee - txb.addOutput(changeAddress, unspentAmount - amountToOutput - feeInSatoshis); + psbt.addOutput({ + address: changeAddress, + value: unspentAmount - amountToOutput - feeInSatoshis, + }); } } for (let c = 0; c < utxos.length; c++) { - txb.sign({ - prevOutScriptType: 'p2sh-p2wpkh', - vin: c, - keyPair, - redeemScript, - witnessValue: parseInt((utxos[c].amount * 100000000).toFixed(0)), - }); + psbt.signInput(c, keyPair); } - let tx = txb.build(); + let tx = psbt.finalizeAllInputs().extractTransaction(); return tx.toHex(); }; @@ -188,12 +214,31 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta highestSequence = i.sequence; } } + let keyPair = bitcoinjs.ECPair.fromWIF(WIF); + let p2wpkh = _p2wpkh({ + pubkey: keyPair.publicKey, + }); + let p2sh = _p2sh({ + redeem: p2wpkh, + }); // creating TX - let txb = new bitcoinjs.TransactionBuilder(); - txb.setVersion(1); + let psbt = new bitcoinjs.Psbt(); + psbt.setVersion(1); for (let unspent of tx.ins) { - txb.addInput(unspent.hash.reverse().toString('hex'), unspent.index, highestSequence + 1); + let txid = Buffer.from(unspent.hash).reverse().toString('hex'); + let index = unspent.index; + let amount = utxodata[txid][index]; + psbt.addInput({ + hash: txid, + index, + sequence: highestSequence + 1, + witnessUtxo: { + script: p2sh.output, + value: amount, + }, + redeemScript: p2wpkh.output, + }); } for (let o of tx.outs) { @@ -201,33 +246,26 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta if (addressReplaceMap[outAddress]) { // means this is DESTINATION address, not messing with it's amount // but replacing the address itseld - txb.addOutput(addressReplaceMap[outAddress], o.value); + psbt.addOutput({ + address: addressReplaceMap[outAddress], + value: o.value, + }); } else { // CHANGE address, so we deduct increased fee from here let feeDeltaInSatoshi = parseInt((feeDelta * 100000000).toFixed(0)); - txb.addOutput(outAddress, o.value - feeDeltaInSatoshi); + psbt.addOutput({ + address: outAddress, + value: o.value - feeDeltaInSatoshi, + }); } } // signing - let keyPair = bitcoinjs.ECPair.fromWIF(WIF); - let redeemScript = bitcoinjs.payments.p2wpkh({ - pubkey: keyPair.publicKey, - }).output; for (let c = 0; c < tx.ins.length; c++) { - let txid = tx.ins[c].hash.reverse().toString('hex'); - let index = tx.ins[c].index; - let amount = utxodata[txid][index]; - txb.sign({ - prevOutScriptType: 'p2sh-p2wpkh', - vin: c, - keyPair, - redeemScript, - witnessValue: amount, - }); + psbt.signInput(c, keyPair); } - let newTx = txb.build(); + let newTx = psbt.finalizeAllInputs().extractTransaction(); return newTx.toHex(); }; diff --git a/tests/unit/signer.js b/tests/unit/signer.js index 14099ff7..c1ca5819 100644 --- a/tests/unit/signer.js +++ b/tests/unit/signer.js @@ -30,8 +30,8 @@ describe('unit - signer', function () { let txhex = '0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a90000000002905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ace00f97000000000017a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d38702483045022100bd687693e57161282a80affb82f18386cbf319bca72ca2c16320b0f3b087bee802205e22a9a16b86628ea08eab83aebec1348c476e9d0c90cd41aa73c47f50d86aab0121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000' let signer = require('../../models/signer') let dummyUtxodata = { - '15b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e': { // txid we use output from - 1: 666 // output index and it's value in satoshi + '1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715': { // txid we use output from + 1: 10000000 // output index and it's value in satoshi } } let newhex = signer.createRBFSegwitTransaction(txhex, {'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr': '3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'}, 0.0001, 'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', dummyUtxodata) From a844ef7efcaabbb9617ce372fe4c02c42e40824e Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 14 Sep 2019 09:51:45 +0900 Subject: [PATCH 3/4] Fix linter --- class/hd-segwit-bech32-wallet.js | 1 + models/signer.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/class/hd-segwit-bech32-wallet.js b/class/hd-segwit-bech32-wallet.js index fe4791d8..8c9247a9 100644 --- a/class/hd-segwit-bech32-wallet.js +++ b/class/hd-segwit-bech32-wallet.js @@ -709,6 +709,7 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { psbt.addInput({ hash: input.txId, index: input.vout, + sequence, witnessUtxo: { script: p2wpkh.output, value: input.value, diff --git a/models/signer.js b/models/signer.js index 2e6dd3b0..737d8f2c 100644 --- a/models/signer.js +++ b/models/signer.js @@ -226,7 +226,9 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta let psbt = new bitcoinjs.Psbt(); psbt.setVersion(1); for (let unspent of tx.ins) { - let txid = Buffer.from(unspent.hash).reverse().toString('hex'); + let txid = Buffer.from(unspent.hash) + .reverse() + .toString('hex'); let index = unspent.index; let amount = utxodata[txid][index]; psbt.addInput({ From d0b2af32a1b633838040eee0677db0216f6d9599 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 14 Sep 2019 10:05:30 +0900 Subject: [PATCH 4/4] Fix outputs value greater than inputs value bug in tests --- screen/selftest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screen/selftest.js b/screen/selftest.js index 38c724f0..8fcdeca5 100644 --- a/screen/selftest.js +++ b/screen/selftest.js @@ -166,7 +166,7 @@ export default class Selftest extends Component { block_height: 514991, tx_input_n: -1, tx_output_n: 2, - value: 546, + value: 110000, ref_balance: 546, spent: false, confirmations: 9, @@ -185,7 +185,7 @@ export default class Selftest extends Component { } if ( tx !== - '0100000000010123c3e0950eabb2b5c1a956e9d003c516fc29ff529211bd552be719fb78ea5e0f0200000017160014597ce022baa887799951e0496c769d9cc0c759dc0000000001a0860100000000001976a914ff715fb722cb10646d80709aeac7f2f4ee00278f88ac0247304402202507d6b05ab19c7fdee217e97fddab80d481d7b2a103c00cecfc634bf897188d02205fa62ad413b6e441f99f94d7d8f9cd4ba51a1d928cbdec6873fa915236dd6d92012103aea0dfd576151cb399347aa6732f8fdf027b9ea3ea2e65fb754803f776e0a50900000000' + '0100000000010123c3e0950eabb2b5c1a956e9d003c516fc29ff529211bd552be719fb78ea5e0f0200000017160014597ce022baa887799951e0496c769d9cc0c759dc0000000001a0860100000000001976a914ff715fb722cb10646d80709aeac7f2f4ee00278f88ac02473044022075670317a0e5b5d4eef154b03db97396a64cbc6ef3b576d98367e1a83c1c488002206d6df1e8085fd711d6ea264de3803340f80fa2c6e30683879d9ad40f3228c56c012103aea0dfd576151cb399347aa6732f8fdf027b9ea3ea2e65fb754803f776e0a50900000000' ) { errorMessage += 'created tx hex doesnt match; '; isOk = false;