From a34605224aedce58118fbb6f7317b69afbe00ba3 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Tue, 21 Jun 2022 13:43:28 +0100 Subject: [PATCH] FIX: better support parsing taproot addresses from Electrum (rel #4749) --- blue_modules/BlueElectrum.d.ts | 2 ++ blue_modules/BlueElectrum.js | 8 +++++++- class/index.js | 1 + class/wallets/taproot-wallet.js | 23 +++++++++++++++++++++++ tests/integration/BlueElectrum.test.js | 7 +++++++ tests/unit/taproot-wallet.test.js | 13 +++++++++++++ 6 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 class/wallets/taproot-wallet.js create mode 100644 tests/unit/taproot-wallet.test.js diff --git a/blue_modules/BlueElectrum.d.ts b/blue_modules/BlueElectrum.d.ts index 74ec97ca4..ad2c8ed10 100644 --- a/blue_modules/BlueElectrum.d.ts +++ b/blue_modules/BlueElectrum.d.ts @@ -85,3 +85,5 @@ export function estimateFees(): Promise<{ fast: number; medium: number; slow: nu export function broadcastV2(txhex: string): Promise; export function getTransactionsFullByAddress(address: string): Promise; + +export function txhexToElectrumTransaction(txhes: string): Transaction; diff --git a/blue_modules/BlueElectrum.js b/blue_modules/BlueElectrum.js index 2754299f4..7717c0a3f 100644 --- a/blue_modules/BlueElectrum.js +++ b/blue_modules/BlueElectrum.js @@ -1,6 +1,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { Alert } from 'react-native'; -import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../class'; +import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class'; import DefaultPreference from 'react-native-default-preference'; import loc from '../loc'; import WidgetCommunication from './WidgetCommunication'; @@ -1030,6 +1030,9 @@ function txhexToElectrumTransaction(txhex) { } else if (LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex'))) { address = LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex')); type = '???'; // TODO + } else { + address = TaprootWallet.scriptPubKeyToAddress(out.script.toString('hex')); + type = 'witness_v1_taproot'; } ret.vout.push({ @@ -1047,3 +1050,6 @@ function txhexToElectrumTransaction(txhex) { } return ret; } + +// exported only to be used in unit tests +module.exports.txhexToElectrumTransaction = txhexToElectrumTransaction; diff --git a/class/index.js b/class/index.js index faa5f6346..e22739dd7 100644 --- a/class/index.js +++ b/class/index.js @@ -2,6 +2,7 @@ export * from './app-storage'; export * from './wallets/abstract-wallet'; export * from './wallets/legacy-wallet'; export * from './wallets/segwit-bech32-wallet'; +export * from './wallets/taproot-wallet'; export * from './wallets/segwit-p2sh-wallet'; export * from './wallets/hd-segwit-p2sh-wallet'; export * from './wallets/hd-legacy-breadwallet-wallet'; diff --git a/class/wallets/taproot-wallet.js b/class/wallets/taproot-wallet.js new file mode 100644 index 000000000..67e47d498 --- /dev/null +++ b/class/wallets/taproot-wallet.js @@ -0,0 +1,23 @@ +import { SegwitBech32Wallet } from './segwit-bech32-wallet'; +const bitcoin = require('bitcoinjs-lib'); + +export class TaprootWallet extends SegwitBech32Wallet { + static type = 'taproot'; + static typeReadable = 'P2 TR'; + static segwitType = 'p2wpkh'; + + /** + * Converts script pub key to a Taproot address if it can. Returns FALSE if it cant. + * + * @param scriptPubKey + * @returns {boolean|string} Either bech32 address or false + */ + static scriptPubKeyToAddress(scriptPubKey) { + try { + const publicKey = Buffer.from(scriptPubKey, 'hex'); + return bitcoin.address.fromOutputScript(publicKey, bitcoin.networks.bitcoin); + } catch (_) { + return false; + } + } +} diff --git a/tests/integration/BlueElectrum.test.js b/tests/integration/BlueElectrum.test.js index 83a77603b..d3788d9ff 100644 --- a/tests/integration/BlueElectrum.test.js +++ b/tests/integration/BlueElectrum.test.js @@ -168,6 +168,13 @@ describe('BlueElectrum', () => { } }); + it('BlueElectrum can do txhexToElectrumTransaction()', () => { + const tx = + '0200000000010137d07edbc9db9a072a79c6f03e7274e52642d64d760143adc64832501087f37b00000000000000008002102700000000000022512040ef293a8a0ebaf8b351a27d89ff4b5b3822a635e4afdca77a30170c363bafa3e4ad0b00000000001600147dfe2249fa56a2f2b4b7ed3b16ee55e7c565198002483045022100e5b9f1c12e133ef659a0e5cc417b1f8625ba9e951bc7083408de2a33d6fb1a84022035ebb1e2d4ab620ee178dc6cd0b58c54123d1526d9a1b3efba612ea80e48edd101210295b56fc62cdd09c200ce19f873d5ddb3074f7141b2533448829385f48f093a1600000000'; + const decoded = BlueElectrum.txhexToElectrumTransaction(tx); + assert.strictEqual(decoded.vout[0].scriptPubKey.addresses[0], 'bc1pgrhjjw52p6a03v635f7cnl6ttvuz9f34ujhaefm6xqtscd3m473szkl92g'); + }); + it.each([false, true])('BlueElectrum can do multiGetBalanceByAddress(), disableBatching=%p', async function (diableBatching) { if (diableBatching) BlueElectrum.setBatchingDisabled(); const balances = await BlueElectrum.multiGetBalanceByAddress([ diff --git a/tests/unit/taproot-wallet.test.js b/tests/unit/taproot-wallet.test.js new file mode 100644 index 000000000..ba9a58989 --- /dev/null +++ b/tests/unit/taproot-wallet.test.js @@ -0,0 +1,13 @@ +import { TaprootWallet } from '../../class'; +const assert = require('assert'); + +describe('Taproot wallet', () => { + it('can convert scriptPubKey to address', () => { + let address = TaprootWallet.scriptPubKeyToAddress('512040ef293a8a0ebaf8b351a27d89ff4b5b3822a635e4afdca77a30170c363bafa3'); + assert.strictEqual(address, 'bc1pgrhjjw52p6a03v635f7cnl6ttvuz9f34ujhaefm6xqtscd3m473szkl92g'); + address = TaprootWallet.scriptPubKeyToAddress(); + assert.strictEqual(address, false); + address = TaprootWallet.scriptPubKeyToAddress('trololo'); + assert.strictEqual(address, false); + }); +});