From 0d353a0932150fd10c9b25cfbad48a52c9796c88 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 1 Sep 2026 13:42:33 +0200 Subject: [PATCH] feat: enhance hardware wallet compatibility and improve transaction handling (#3517) * feat: enhance hardware wallet compatibility and improve transaction handling - Updated hardware wallet services for Bitcoin and Litecoin (Ledger & Trezor). - Added `LitecoinTrezorService` for signing and managing Litecoin transactions. - Improved `PSBTTransaction` builder to handle change outputs and derivation paths. - Fixed exception handling in `wallet_hardware_restore_view_model`. - Refactored output and input structures to ensure consistency and extend compatibility. * refactor: address comments * refactor: address comments * refactor: address comments --- cw_bitcoin/lib/bitcoin_wallet.dart | 64 +++-- cw_bitcoin/lib/electrum_wallet.dart | 7 +- cw_bitcoin/lib/hardware/bitbox_service.dart | 4 +- .../bitcoin_hardware_wallet_service.dart | 9 +- .../lib/hardware/litecoin_ledger_service.dart | 89 +++--- .../lib/hardware/litecoin_trezor_service.dart | 100 +++++++ cw_bitcoin/lib/hardware/trezor_service.dart | 268 +++++++++--------- cw_bitcoin/lib/litecoin_wallet.dart | 48 +++- cw_bitcoin/lib/psbt/transaction_builder.dart | 175 ++++++++---- cw_bitcoin/pubspec.yaml | 5 + cw_monero/pubspec.yaml | 4 +- lib/bitcoin/cw_bitcoin.dart | 6 +- lib/router.dart | 25 +- .../trezor_connect_view_model.dart | 8 +- .../wallet_hardware_restore_view_model.dart | 2 +- pubspec_overrides.yaml | 8 +- tool/configure.dart | 4 +- 17 files changed, 521 insertions(+), 305 deletions(-) create mode 100644 cw_bitcoin/lib/hardware/litecoin_trezor_service.dart diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index ac40de051..a583dcc01 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -430,7 +430,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { .isNotEmpty; Future buildPsbt({ - required List outputs, + required List outputs, required List cwOutputs, required BigInt fee, required BasedUtxoNetwork network, @@ -447,33 +447,65 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; - psbtReadyInputs.add(PSBTReadyUtxoWithAddress( - utxo: utxo.utxo, - rawTx: rawTx, - ownerDetails: utxo.ownerDetails, - ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - ownerMasterFingerprint: masterFingerprint, - ownerPublicKey: publicKeyAndDerivationPath.publicKey, - )); + psbtReadyInputs.add( + PSBTReadyUtxoWithAddress( + utxo: utxo.utxo, + rawTx: rawTx, + ownerDetails: utxo.ownerDetails, + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, + ownerMasterFingerprint: masterFingerprint, + ownerPublicKey: publicKeyAndDerivationPath.publicKey, + ), + ); } + final psbtReadyOutputs = outputs.map((o) { + final cwOutput = cwOutputs + .where( + (e) => [e.address, e.extractedAddress] + .map((e) => e?.toLowerCase()) + .contains(o.address.toAddress().toLowerCase()), + ) + .firstOrNull; + + if (o.isChange && publicKeys.containsKey(o.address.pubKeyHash())) { + final changeKey = publicKeys[o.address.pubKeyHash()]!; + return PSBTReadyBitcoinOutput( + address: o.address, + value: o.value, + isSilentPayment: o.isSilentPayment, + isChange: o.isChange, + changeMasterFingerprint: masterFingerprint, + changeDerivationPath: changeKey.derivationPath, + changePublicKey: changeKey.publicKey, + outputInfo: cwOutput, + ); + } + return PSBTReadyBitcoinOutput( + address: o.address, + value: o.value, + isSilentPayment: o.isSilentPayment, + isChange: o.isChange, + outputInfo: cwOutput, + ); + }).toList(); + final locktime = antiFeeSnipingLocktime( chainTip: await getCurrentChainTip(), synced: syncStatus is SyncedSyncStatus, ); return PSBTTransactionBuild( - inputs: psbtReadyInputs, - outputs: outputs, - enableRBF: enableRBF, - cwOutputs: cwOutputs, - locktime: locktime) - .psbt; + inputs: psbtReadyInputs, + outputs: psbtReadyOutputs, + enableRBF: enableRBF, + locktime: locktime, + ).psbt; } @override Future buildHardwareWalletTransaction({ - required List outputs, + required List outputs, required BigInt fee, required BasedUtxoNetwork network, required List utxos, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 1da016931..8b1cc99fb 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1283,8 +1283,11 @@ abstract class ElectrumWalletBase final changeDerivationPath = "${_hardenedDerivationPath(changeBaseDerivationPath)}" "/${changeAddress.isHidden ? "1" : "0"}" "/${changeAddress.index}"; + final changeHd = _hdFor(record: changeAddress).childKey(Bip32KeyIndex(changeAddress.index)); + final changePubKeyHex = changeHd.publicKey.toHex(); + utxoDetails.publicKeys[address.pubKeyHash()] = - PublicKeyWithDerivationPath('', changeDerivationPath); + PublicKeyWithDerivationPath(changePubKeyHex, changeDerivationPath); // calcFee updates the silent payment outputs to calculate the tx size accounting // for taproot addresses, but if more inputs are needed to make up for fees, @@ -1737,7 +1740,7 @@ abstract class ElectrumWalletBase HardwareWalletService? hardwareWalletService; Future buildHardwareWalletTransaction({ - required List outputs, + required List outputs, required BigInt fee, required BasedUtxoNetwork network, required List utxos, diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart index a71dcacbc..4b3ea5915 100644 --- a/cw_bitcoin/lib/hardware/bitbox_service.dart +++ b/cw_bitcoin/lib/hardware/bitbox_service.dart @@ -106,11 +106,11 @@ class LitecoinBitboxService extends HardwareWalletService @override Future signLitecoinTransaction({ - required List outputs, + required List outputs, required List inputs, required Map publicKeys, }) async { - final psbt = PSBTTransactionBuild(inputs: inputs, outputs: outputs, cwOutputs: []).psbt; + final psbt = PSBTTransactionBuild(inputs: inputs, outputs: outputs).psbt; final signedPsbt = await manager.signBTCPsbt(bitboxCoinType, base64Encode(psbt.asPsbtV0())); final transactionRes = PsbtV2() diff --git a/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart index 885868d3c..22954d113 100644 --- a/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart @@ -1,8 +1,7 @@ -import 'dart:typed_data'; +import "dart:typed_data"; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_bitcoin/psbt/transaction_builder.dart'; +import "package:cw_bitcoin/electrum_wallet.dart"; +import "package:cw_bitcoin/psbt/transaction_builder.dart"; mixin BitcoinHardwareWalletService { Future getMasterFingerprint() async => Uint8List.fromList([0, 0, 0, 0]); @@ -10,7 +9,7 @@ mixin BitcoinHardwareWalletService { mixin LitecoinHardwareWalletService on BitcoinHardwareWalletService { Future signLitecoinTransaction({ - required List outputs, + required List outputs, required List inputs, required Map publicKeys, }); diff --git a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart index aa73fc106..852f77881 100644 --- a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart +++ b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart @@ -1,16 +1,16 @@ -import 'dart:async'; -import 'dart:typed_data'; +import "dart:async"; +import "dart:typed_data"; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart'; -import 'package:cw_bitcoin/psbt/transaction_builder.dart'; -import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_core/hardware/hardware_account_data.dart'; -import 'package:cw_core/hardware/hardware_wallet_service.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; -import 'package:ledger_litecoin/ledger_litecoin.dart'; +import "package:bitcoin_base/bitcoin_base.dart"; +import "package:blockchain_utils/blockchain_utils.dart"; +import "package:cw_bitcoin/electrum_wallet.dart"; +import "package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart"; +import "package:cw_bitcoin/psbt/transaction_builder.dart"; +import "package:cw_bitcoin/utils.dart"; +import "package:cw_core/hardware/hardware_account_data.dart"; +import "package:cw_core/hardware/hardware_wallet_service.dart"; +import "package:ledger_flutter_plus/ledger_flutter_plus.dart"; +import "package:ledger_litecoin/ledger_litecoin.dart"; class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService { @@ -31,18 +31,21 @@ class LitecoinLedgerService extends HardwareWalletService for (final i in indexRange) { final derivationPath = "m/84'/2'/$i'"; final xpub = await litecoinLedgerApp.getXPubKey( - accountsDerivationPath: derivationPath, - xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16)); + accountsDerivationPath: derivationPath, + xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16), + ); final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0)); final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); - accounts.add(HardwareAccountData( - address: address, - accountIndex: i, - derivationPath: derivationPath, - xpub: xpub, - )); + accounts.add( + HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + xpub: xpub, + ), + ); } return accounts; @@ -50,7 +53,7 @@ class LitecoinLedgerService extends HardwareWalletService @override Future signLitecoinTransaction({ - required List outputs, + required List outputs, required List inputs, required Map publicKeys, }) { @@ -58,31 +61,39 @@ class LitecoinLedgerService extends HardwareWalletService for (final utxo in inputs) { final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; - readyInputs.add(LedgerTransaction( - rawTx: utxo.rawTx, - outputIndex: utxo.utxo.vout, - ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)), - ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - )); + readyInputs.add( + LedgerTransaction( + rawTx: utxo.rawTx, + outputIndex: utxo.utxo.vout, + ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)), + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, + ), + ); } // Check if we have the key to one of the output addresses to hide change on the device String? changePath; for (final output in outputs) { - final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; - if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; + if (output.isChange) { + changePath ??= output.changeDerivationPath; + } } return litecoinLedgerApp.createTransaction( - inputs: readyInputs, - outputs: outputs - .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value, - Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) - .toList(), - changePath: changePath, - sigHashType: 0x01, - additionals: ["bech32"], - isSegWit: true, - useTrustedInputForSegwit: true); + inputs: readyInputs, + outputs: outputs + .map( + (e) => TransactionOutput.fromBigInt( + (e as BitcoinOutput).value, + Uint8List.fromList(e.address.toScriptPubKey().toBytes()), + ), + ) + .toList(), + changePath: changePath, + sigHashType: 0x01, + additionals: ["bech32"], + isSegWit: true, + useTrustedInputForSegwit: true, + ); } } diff --git a/cw_bitcoin/lib/hardware/litecoin_trezor_service.dart b/cw_bitcoin/lib/hardware/litecoin_trezor_service.dart new file mode 100644 index 000000000..4858e30fe --- /dev/null +++ b/cw_bitcoin/lib/hardware/litecoin_trezor_service.dart @@ -0,0 +1,100 @@ +import 'dart:async'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart'; +import 'package:cw_bitcoin/psbt/transaction_builder.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:trezor_connect/trezor_connect.dart'; + +class LitecoinTrezorService extends HardwareWalletService + with BitcoinHardwareWalletService, LitecoinHardwareWalletService { + LitecoinTrezorService(this.connect); + + final TrezorConnect connect; + + @override + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final indexRange = List.generate(limit, (i) => i + index); + final requestParams = []; + final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer; + + for (final i in indexRange) { + final derivationPath = "m/84'/2'/$i'"; + requestParams.add(TrezorGetPublicKeyParams(path: derivationPath, coin: "LTC")); + } + + final accounts = await connect.getPublicKeyBundle(requestParams); + + return accounts?.map((account) { + final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub, xpubVersion) + .childKey(Bip32KeyIndex(0)); + + final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); + return HardwareAccountData( + address: address, + xpub: account.xpub, + accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index + derivationPath: account.serializedPath, + ); + }).toList() ?? + []; + } + + @override + Future signLitecoinTransaction({ + required List outputs, + required List inputs, + required Map publicKeys, + }) async { + final readyInputs = inputs + .map((input) => TrezorTxInput( + prevHash: input.utxo.txHash, + prevIndex: input.utxo.vout, + amount: input.utxo.value.toInt(), + addressPath: Bip32PathParser.parse(input.ownerDerivationPath).toList(), + scriptType: "SPENDWITNESS", + )) + .toList(); + + final readyOutputs = outputs.map((output) { + final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; + + return TrezorTxOutput( + amount: output.toOutput.amount.toInt(), + address: maybeChangePath != null + ? null + : output.toOutput.scriptPubKey.toAddress(network: LitecoinNetwork.mainnet), + scriptType: _getScriptType(output.toOutput.scriptPubKey.getAddressType()!), + addressPath: maybeChangePath != null + ? Bip32PathParser.parse(maybeChangePath.derivationPath).toList() + : null, + ); + }).toList(); + + final signedTx = + await connect.signTransaction(coin: 'LTC', inputs: readyInputs, outputs: readyOutputs); + + return signedTx!.serializedTx; + } +} + +String _getScriptType(BitcoinAddressType addressType) { + switch (addressType) { + case P2pkhAddressType.p2pkh: + return "PAYTOADDRESS"; + case P2shAddressType.p2wpkhInP2sh: + return "PAYTOSCRIPTHASH"; + case SegwitAddresType.p2tr: + return "PAYTOTAPROOT"; + case SegwitAddresType.p2wsh: + return "PAYTOP2SHWITNESS"; + case SegwitAddresType.p2wpkh: + return "PAYTOWITNESS"; + default: + throw Exception("Unknown Address Type"); + } +} diff --git a/cw_bitcoin/lib/hardware/trezor_service.dart b/cw_bitcoin/lib/hardware/trezor_service.dart index c9071ccce..238a01ee0 100644 --- a/cw_bitcoin/lib/hardware/trezor_service.dart +++ b/cw_bitcoin/lib/hardware/trezor_service.dart @@ -1,190 +1,178 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart'; -import 'package:cw_bitcoin/psbt/transaction_builder.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:flutter/foundation.dart'; import 'package:ledger_bitcoin/psbt.dart'; -import 'package:trezor_connect/trezor_connect.dart'; +import 'package:ledger_bitcoin/src/utils/uint8list_extension.dart' as ext; +import 'package:trezor_flutter/trezor_flutter.dart' as sdk; class BitcoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService { - BitcoinTrezorService(this.connect); + BitcoinTrezorService(this.client) : _trezorBitcoin = sdk.TrezorBitcoin(client); - final TrezorConnect connect; + final sdk.TrezorClient client; + final sdk.TrezorBitcoin _trezorBitcoin; + + @override + Future getMasterFingerprint() async { + final publicKey = await _trezorBitcoin.getPublicKey(derivationPath: "m/84'/0'/0'"); + final fingerprintBuffer = ByteData(4)..setUint32(0, publicKey.$2); + return fingerprintBuffer.buffer.asUint8List(); + } @override Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final accounts = []; final indexRange = List.generate(limit, (i) => i + index); - final requestParams = []; for (final i in indexRange) { - requestParams.add(TrezorGetPublicKeyParams(path: "m/84'/0'/$i'")); + final derivationPath = "m/84'/0'/$i'"; + final xPub = + await _trezorBitcoin.getPublicKey(derivationPath: derivationPath, ignoreXpubMagic: true); + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub.$1).childKey(Bip32KeyIndex(0)); + + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); + + accounts.add( + HardwareAccountData( + address: address, + accountIndex: i, + derivationPath: derivationPath, + xpub: xPub.$1, + ), + ); } - final accounts = await connect.getPublicKeyBundle(requestParams); - - return accounts?.map((account) { - final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub).childKey(Bip32KeyIndex(0)); - final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); - return HardwareAccountData( - address: address, - xpub: account.xpub, - accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index - derivationPath: account.serializedPath, - ); - }).toList() ?? - []; + return accounts; } @override Future signTransaction({required String transaction}) async { final psbt = PsbtV2()..deserialize(base64Decode(transaction)); + final masterFingerprint = await getMasterFingerprint(); + + final inputs = []; + final prevTxs = {}; - final inputs = []; final inputCount = psbt.getGlobalInputCount(); for (var i = 0; i < inputCount; i++) { - final inputTxRaw = psbt.getInputNonWitnessUtxo(i); - final inputTx = BtcTransaction.fromRaw(hex.encode(inputTxRaw!)); - final inputOutputIndex = psbt.getInputOutputIndex(i); + final rawPrevTx = psbt.getInputNonWitnessUtxo(i); + if (rawPrevTx == null) { + throw StateError("PSBT input $i is missing its previous transaction"); + } + final prevTx = BtcTransaction.fromRaw(hex.encode(rawPrevTx)); + final vout = psbt.getInputOutputIndex(i); - final publicKeys = psbt.inputMaps[i].keys.where((e) => e.startsWith("06")); - final pubkey = Uint8List.fromList(hex.decode(publicKeys.first.substring(2))); + final txidBytes = psbt.getInputPreviousTxid(i).reversed.toList(); - inputs.add(TrezorTxInput( - prevHash: hex.encode(psbt.getInputPreviousTxid(i).reversed.toList()), - prevIndex: inputOutputIndex, - amount: inputTx.outputs[inputOutputIndex].amount.toInt(), - addressPath: psbt.getInputBip32Derivation(i, pubkey)!.$2, + final pubkey = _firstDerivationPubkey(psbt.inputMaps[i], "06"); + if (pubkey == null) { + throw StateError("PSBT input $i is missing its BIP32 derivation"); + } + + final derivation = psbt.getInputBip32Derivation(i, pubkey)!; + final path = derivation.$2; + + if (!listEquals(derivation.$1, masterFingerprint)) { + throw Exception("Fingerprint missmatch with the PSBT"); + } + + inputs.add( + sdk.TrezorTxInput( + addressPath: path, + prevHash: txidBytes, + prevIndex: vout, + amount: prevTx.outputs[vout].amount.toInt(), sequence: psbt.getInputSequence(i), - scriptType: "SPENDWITNESS")); + scriptType: "SPENDWITNESS", + ), + ); + + prevTxs[hex.encode(txidBytes)] = sdk.TrezorPrevTx( + meta: sdk.TrezorPrevTxMeta( + version: Uint8List.fromList(prevTx.version).readUint32LE(0), + lockTime: Uint8List.fromList(prevTx.locktime).readUint32LE(0), + inputsCount: prevTx.inputs.length, + outputsCount: prevTx.outputs.length, + ), + inputs: prevTx.inputs + .map( + (e) => sdk.TrezorPrevInput( + prevHash: hex.decode(e.txId), + prevIndex: e.txIndex, + scriptSig: e.scriptSig.toBytes(), + sequence: Uint8List.fromList(e.sequence).readUint32LE(0), + ), + ) + .toList(), + outputs: prevTx.outputs + .map( + (e) => sdk.TrezorPrevOutput( + amount: e.amount.toInt(), + scriptPubkey: e.scriptPubKey.toBytes(), + ), + ) + .toList(), + ); } - final outputs = []; + final outputs = []; final outputCount = psbt.getGlobalOutputCount(); for (var i = 0; i < outputCount; i++) { + final amount = psbt.getOutputAmount(i); + + // An output carrying our own BIP32 derivation is change: identify it + // by path so the device verifies it internally instead of displaying + // it as a payment. + final changePubkey = _firstDerivationPubkey(psbt.outputMaps[i], "02"); + if (changePubkey != null) { + final (fingerprint, path) = psbt.getOutputBip32Derivation(i, changePubkey); + if (listEquals(fingerprint, masterFingerprint)) { + outputs.add(sdk.TrezorTxOutput( + addressPath: path, + amount: amount, + scriptType: "PAYTOWITNESS", + )); + continue; + } + } + final script = Script.fromRaw(byteData: psbt.getOutputScript(i)); - // Trezor's protocol expects script_type: PAYTOADDRESS whenever the - // output is identified by `address` (vs. own-change `addressPath`). - // Suite parses the address string itself to determine the actual - // on-chain script type (P2WPKH, P2TR, P2SH, etc.); script_type is - // only semantically meaningful when addressPath is set. Passing - // PAYTOWITNESS / PAYTOP2SHWITNESS / PAYTOTAPROOT here together with - // `address` causes Suite to reject the deeplink as "Invalid - // parameters from calling app" because those values specifically - // mean "own change paid to that wallet type." - outputs.add(TrezorTxOutput( - amount: psbt.getOutputAmount(i), + outputs.add(sdk.TrezorTxOutput( address: script.toAddress(), + amount: amount, scriptType: "PAYTOADDRESS", - // ToDo: when change-output detection lands, set addressPath + _getScriptType(...) for own-change outputs. - // ToDo: addressPath: psbt.getOutputBip32Derivation(i, pubkey).$2, // To highlight change outputs )); } - final signedTx = await connect.signTransaction(coin: 'btc', inputs: inputs, outputs: outputs); + final signed = await _trezorBitcoin.signTransaction( + inputs: inputs, + outputs: outputs, + prevTxs: prevTxs, + version: psbt.getGlobalTxVersion(), + lockTime: psbt.getGlobalFallbackLocktime() ?? 0, + ); - return Uint8List.fromList(BytesUtils.fromHexString(signedTx!.serializedTx)); + return signed.serializedTx; } @override - Future signMessage({required Uint8List message, String? derivationPath}) async { - final sig = await connect.signMessage(derivationPath ?? "m/84'/0'/0'/0/0", - message: hex.encode(message), hex: true); - return base64Decode(sig!.signature); - } -} - -class LitecoinTrezorService extends HardwareWalletService - with BitcoinHardwareWalletService, LitecoinHardwareWalletService { - LitecoinTrezorService(this.connect); - - final TrezorConnect connect; - - @override - Future> getAvailableAccounts({int index = 0, int limit = 5}) async { - final indexRange = List.generate(limit, (i) => i + index); - final requestParams = []; - final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer; - - for (final i in indexRange) { - final derivationPath = "m/84'/2'/$i'"; - requestParams.add(TrezorGetPublicKeyParams(path: derivationPath, coin: "LTC")); - } - - final accounts = await connect.getPublicKeyBundle(requestParams); - - return accounts?.map((account) { - final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub, xpubVersion) - .childKey(Bip32KeyIndex(0)); - - final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet); - return HardwareAccountData( - address: address, - xpub: account.xpub, - accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index - derivationPath: account.serializedPath, - ); - }).toList() ?? - []; - } - - @override - Future signLitecoinTransaction({ - required List outputs, - required List inputs, - required Map publicKeys, - }) async { - final readyInputs = inputs - .map((input) => TrezorTxInput( - prevHash: input.utxo.txHash, - prevIndex: input.utxo.vout, - amount: input.utxo.value.toInt(), - addressPath: Bip32PathParser.parse(input.ownerDerivationPath).toList(), - scriptType: "SPENDWITNESS", - )) - .toList(); - - final readyOutputs = outputs.map((output) { - final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; - - return TrezorTxOutput( - amount: output.toOutput.amount.toInt(), - address: maybeChangePath != null - ? null - : output.toOutput.scriptPubKey.toAddress(network: LitecoinNetwork.mainnet), - scriptType: _getScriptType(output.toOutput.scriptPubKey.getAddressType()!), - addressPath: maybeChangePath != null - ? Bip32PathParser.parse(maybeChangePath.derivationPath).toList() - : null, + Future signMessage({required Uint8List message, String? derivationPath}) => + _trezorBitcoin.signMessage( + derivationPath: derivationPath ?? "m/84'/0'/0'/0/0", + message: message, ); - }).toList(); - final signedTx = - await connect.signTransaction(coin: 'LTC', inputs: readyInputs, outputs: readyOutputs); - - return signedTx!.serializedTx; - } -} - -String _getScriptType(BitcoinAddressType addressType) { - switch (addressType) { - case P2pkhAddressType.p2pkh: - return "PAYTOADDRESS"; - case P2shAddressType.p2wpkhInP2sh: - return "PAYTOSCRIPTHASH"; - case SegwitAddresType.p2tr: - return "PAYTOTAPROOT"; - case SegwitAddresType.p2wsh: - return "PAYTOP2SHWITNESS"; - case SegwitAddresType.p2wpkh: - return "PAYTOWITNESS"; - default: - throw Exception("Unknown Address Type"); + /// Extracts the pubkey from the first BIP32-derivation record + /// ([keyTypeHex] `06` for inputs, `02` for outputs) of a PSBT key-value map. + Uint8List? _firstDerivationPubkey(Map map, String keyTypeHex) { + final key = map.keys.where((mapKey) => mapKey.startsWith(keyTypeHex)).firstOrNull; + if (key == null) return null; + return Uint8List.fromList(hex.decode(key.substring(2))); } } diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index aa8fcd330..fbd946233 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1556,7 +1556,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @override Future buildHardwareWalletTransaction({ - required List outputs, + required List outputs, required BigInt fee, required BasedUtxoNetwork network, required List utxos, @@ -1568,28 +1568,48 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, }) async { final masterFingerprint = - await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint(); + await (hardwareWalletService! as BitcoinHardwareWalletService).getMasterFingerprint(); final readyInputs = []; for (final utxo in utxos) { final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; - readyInputs.add(PSBTReadyUtxoWithAddress( - utxo: utxo.utxo, - rawTx: rawTx, - ownerDetails: utxo.ownerDetails, - ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - ownerMasterFingerprint: masterFingerprint, - ownerPublicKey: publicKeyAndDerivationPath.publicKey, - )); + readyInputs.add( + PSBTReadyUtxoWithAddress( + utxo: utxo.utxo, + rawTx: rawTx, + ownerDetails: utxo.ownerDetails, + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, + ownerMasterFingerprint: masterFingerprint, + ownerPublicKey: publicKeyAndDerivationPath.publicKey, + ), + ); } - final orderedOutputs = orderOutputs(outputs, outputOrdering); + final orderedOutputs = + orderOutputs(outputs, outputOrdering).map((o) { + if (o.isChange && publicKeys.containsKey(o.address.pubKeyHash())) { + final changeKey = publicKeys[o.address.pubKeyHash()]!; + return PSBTReadyBitcoinOutput( + address: o.address, + value: o.value, + isSilentPayment: o.isSilentPayment, + isChange: o.isChange, + changeMasterFingerprint: masterFingerprint, + changeDerivationPath: changeKey.derivationPath, + changePublicKey: changeKey.publicKey, + ); + } + return PSBTReadyBitcoinOutput.fromOutput(o); + }); - final rawHex = await (hardwareWalletService as LitecoinHardwareWalletService) - .signLitecoinTransaction( - outputs: orderedOutputs, inputs: readyInputs, publicKeys: publicKeys); + final rawHex = + await (hardwareWalletService! as LitecoinHardwareWalletService).signLitecoinTransaction( + outputs: orderedOutputs.toList(), + inputs: readyInputs, + publicKeys: publicKeys, + ); return BtcTransaction.fromRaw(rawHex); } diff --git a/cw_bitcoin/lib/psbt/transaction_builder.dart b/cw_bitcoin/lib/psbt/transaction_builder.dart index ab107bfeb..5eea75794 100644 --- a/cw_bitcoin/lib/psbt/transaction_builder.dart +++ b/cw_bitcoin/lib/psbt/transaction_builder.dart @@ -1,21 +1,19 @@ -import 'dart:convert'; -import 'dart:typed_data'; +import "dart:convert"; +import "dart:typed_data"; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:convert/convert.dart'; -import 'package:cw_core/output_info.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:ledger_bitcoin/psbt.dart'; +import "package:bitcoin_base/bitcoin_base.dart"; +import "package:convert/convert.dart"; +import "package:cw_core/output_info.dart"; +import "package:cw_core/utils/print_verbose.dart"; +import "package:ledger_bitcoin/psbt.dart"; class PSBTTransactionBuild { - final PsbtV2 psbt = PsbtV2(); - - PSBTTransactionBuild( - {required List inputs, - required List outputs, - required List cwOutputs, - bool enableRBF = true, - int locktime = 0}) { + PSBTTransactionBuild({ + required List inputs, + required List outputs, + bool enableRBF = true, + int locktime = 0, + }) { psbt.setGlobalTxVersion(2); psbt.setGlobalFallbackLocktime(locktime); psbt.setGlobalInputCount(inputs.length); @@ -24,12 +22,10 @@ class PSBTTransactionBuild { for (var i = 0; i < inputs.length; i++) { final input = inputs[i]; - printV(input.utxo.isP2tr()); - printV(input.utxo.isSegwit()); - printV(input.utxo.isP2shSegwit()); - psbt.setInputPreviousTxId( - i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList())); + i, + Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()), + ); psbt.setInputOutputIndex(i, input.utxo.vout); psbt.setInputSequence(i, enableRBF ? 0xfffffffd : 0xffffffff); @@ -47,75 +43,98 @@ class PSBTTransactionBuild { for (var i = 0; i < outputs.length; i++) { final output = outputs[i]; - if (output is BitcoinOutput) { - psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes())); - psbt.setOutputAmount(i, output.value.toInt()); - if (cwOutputs.isNotEmpty) { - try { - final cwOutput = cwOutputs - .where((e) => [e.address, e.extractedAddress] - .map((e) => e?.toLowerCase()) - .contains(output.address.toAddress().toLowerCase())) - .firstOrNull; - if (cwOutput != null && - cwOutput.extra.containsKey('bip353_name') && - cwOutput.extra.containsKey('bip353_proof')) { - final bip353Name = utf8.encode(cwOutput.extra['bip353_name'] as String); - final bip353Proof = base64.decode(cwOutput.extra['bip353_proof'] as String); + psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes())); + psbt.setOutputAmount(i, output.value.toInt()); - if (bip353Name.length > 255) { - printV('BIP353 name is too long, skipping'); - continue; - } - final proof = Uint8List.fromList([ - bip353Name.length, - ...bip353Name, - ...bip353Proof, - ]); + if (output.isChange && + output.changeDerivationPath != null && + output.changePublicKey != null && + output.changeMasterFingerprint != null) { + psbt.setOutputBip32Derivation( + i, + Uint8List.fromList(hex.decode(output.changePublicKey!)), + output.changeMasterFingerprint!, + BIPPath.fromString(output.changeDerivationPath!).toPathArray(), + ); + } - psbt.setOutputDNSSECProof(i, proof); + if (output.outputInfo != null) { + try { + final cwOutput = output.outputInfo!; + if (cwOutput.extra.containsKey("bip353_name") && + cwOutput.extra.containsKey("bip353_proof")) { + final bip353Name = utf8.encode(cwOutput.extra["bip353_name"] as String); + final bip353Proof = base64.decode(cwOutput.extra["bip353_proof"] as String); + + if (bip353Name.length > 255) { + printV("BIP353 name is too long, skipping"); + continue; } - } catch (e) { - printV('Error setting DNSSEC proof: $e'); + final proof = Uint8List.fromList([ + bip353Name.length, + ...bip353Name, + ...bip353Proof, + ]); + + psbt.setOutputDNSSECProof(i, proof); } + } catch (e) { + printV("Error setting DNSSEC proof: $e"); } } } } + final PsbtV2 psbt = PsbtV2(); + void setInputP2pkh(int i, PSBTReadyUtxoWithAddress input) { psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx))); - psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)), - input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray()); + psbt.setInputBip32Derivation( + i, + Uint8List.fromList(hex.decode(input.ownerPublicKey)), + input.ownerMasterFingerprint, + BIPPath.fromString(input.ownerDerivationPath).toPathArray(), + ); } void setInputSegwit(int i, PSBTReadyUtxoWithAddress input) { psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx))); - psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)), - input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray()); + psbt.setInputBip32Derivation( + i, + Uint8List.fromList(hex.decode(input.ownerPublicKey)), + input.ownerMasterFingerprint, + BIPPath.fromString(input.ownerDerivationPath).toPathArray(), + ); - psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)), - Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes())); + psbt.setInputWitnessUtxo( + i, + Uint8List.fromList(bigIntToUint64LE(input.utxo.value)), + Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()), + ); } void setInputP2shSegwit(int i, PSBTReadyUtxoWithAddress input) { psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx))); - psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)), - input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray()); + psbt.setInputBip32Derivation( + i, + Uint8List.fromList(hex.decode(input.ownerPublicKey)), + input.ownerMasterFingerprint, + BIPPath.fromString(input.ownerDerivationPath).toPathArray(), + ); psbt.setInputRedeemScript( - i, Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes())); - psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)), - Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes())); + i, + Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()), + ); + psbt.setInputWitnessUtxo( + i, + Uint8List.fromList(bigIntToUint64LE(input.utxo.value)), + Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()), + ); } } class PSBTReadyUtxoWithAddress extends UtxoWithAddress { - final String rawTx; - final String ownerDerivationPath; - final Uint8List ownerMasterFingerprint; - final String ownerPublicKey; - PSBTReadyUtxoWithAddress({ required super.utxo, required this.rawTx, @@ -124,4 +143,34 @@ class PSBTReadyUtxoWithAddress extends UtxoWithAddress { required this.ownerMasterFingerprint, required this.ownerPublicKey, }); + + final String rawTx; + final String ownerDerivationPath; + final Uint8List ownerMasterFingerprint; + final String ownerPublicKey; +} + +class PSBTReadyBitcoinOutput extends BitcoinOutput { + PSBTReadyBitcoinOutput({ + required super.address, + required super.value, + super.isSilentPayment = false, + super.isChange = false, + this.changeDerivationPath, + this.changeMasterFingerprint, + this.changePublicKey, + this.outputInfo, + }); + + factory PSBTReadyBitcoinOutput.fromOutput(BitcoinOutput output) => PSBTReadyBitcoinOutput( + address: output.address, + value: output.value, + isSilentPayment: output.isSilentPayment, + isChange: output.isChange, + ); + + final String? changeDerivationPath; + final Uint8List? changeMasterFingerprint; + final String? changePublicKey; + final OutputInfo? outputInfo; } diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 3eb747a72..922a83f1d 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -55,6 +55,11 @@ dependencies: git: url: https://github.com/cake-tech/trezor_connect ref: d1242cea90f84b00200e7bcab914a5af750e23fb + trezor_flutter: + git: + url: https://github.com/cake-tech/trezor-flutter + ref: f5121b1f3a06000ff836da8e6e6c83f53a9fa487 + path: trezor-flutter bitbox_flutter: path: ../scripts/bitbox_flutter socks_socket: diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 04d3f3997..4e3196955 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -32,8 +32,8 @@ dependencies: ledger_flutter_plus: ^1.4.1 trezor_flutter: git: - url: https://github.com/cake-tech/trezor-flutter.git - ref: 7d27e19b7bcdcd6241df35953f9a6ec2df0c7a97 + url: https://github.com/cake-tech/trezor-flutter + ref: f5121b1f3a06000ff836da8e6e6c83f53a9fa487 path: trezor-flutter dev_dependencies: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index c2343dfe1..4e4789e57 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -558,9 +558,9 @@ class CWBitcoin extends Bitcoin { @override HardwareWalletService getTrezorHardwareWalletService( - trezor.TrezorConnect connect, bool isBitcoin) { - if (isBitcoin) return BitcoinTrezorService(connect); - return LitecoinTrezorService(connect); + trezor.TrezorConnect? connect, trezor.TrezorClient? client, bool isBitcoin) { + if (isBitcoin) return BitcoinTrezorService(client!); + return LitecoinTrezorService(connect!); } @override diff --git a/lib/router.dart b/lib/router.dart index 2b01b079f..643a03cde 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -361,9 +361,10 @@ Route createRoute(RouteSettings settings) { ConnectDevicePageParams( walletType: availableWalletTypes.first, hardwareWalletType: hardwareWalletType, - onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed( - Routes.chooseHardwareWalletAccount, - arguments: [availableWalletTypes.first, hardwareWalletType]), + onConnectDevice: (context, _) => Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [availableWalletTypes.first, hardwareWalletType], + ), isReconnect: false, ), getIt.get(param1: hardwareWalletType), @@ -373,19 +374,23 @@ Route createRoute(RouteSettings settings) { return handleRouteWithPlatformAwareness( (_) => getIt.get( param1: NewWalletTypeArguments( - onTypeSelected: (BuildContext context, WalletType type) { - if (hardwareWalletType == HardwareWalletType.trezor && type != WalletType.monero) { - Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, - arguments: [type, hardwareWalletType]); + onTypeSelected: (context, type) { + if (hardwareWalletType == HardwareWalletType.trezor && + !trezorUseNative.contains(type)) { + Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [type, hardwareWalletType], + ); return; } final arguments = ConnectDevicePageParams( walletType: type, hardwareWalletType: hardwareWalletType, - onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed( - Routes.chooseHardwareWalletAccount, - arguments: [type, hardwareWalletType]), + onConnectDevice: (context, _) => Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [type, hardwareWalletType], + ), isReconnect: false, ); diff --git a/lib/view_model/hardware_wallet/trezor_connect_view_model.dart b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart index 4d65e4d53..6e23881d8 100644 --- a/lib/view_model/hardware_wallet/trezor_connect_view_model.dart +++ b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart @@ -29,6 +29,8 @@ import "package:trezor_flutter/trezor_flutter.dart" as sdk; part "trezor_connect_view_model.g.dart"; +const trezorUseNative = [WalletType.bitcoin, WalletType.monero]; + class TrezorConnectViewModel = TrezorConnectViewModelBase with _$TrezorConnectViewModel; abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with Store { @@ -242,7 +244,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S } @override - bool isConnected(WalletType type) => type == WalletType.monero + bool isConnected(WalletType type) => trezorUseNative.contains(type) ? _client != null && _client?.connection.isDisconnected == false : true; @@ -252,9 +254,9 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S case WalletType.monero: return monero!.getTrezorHardwareWalletService(_client!); case WalletType.bitcoin: - return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true); + return bitcoin!.getTrezorHardwareWalletService(null, _client!, true); case WalletType.litecoin: - return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false); + return bitcoin!.getTrezorHardwareWalletService(trezorConnect, null, false); case WalletType.ethereum: case WalletType.polygon: return evm!.getTrezorHardwareWalletService(trezorConnect); diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index 35de089c8..4d7a48315 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -56,7 +56,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with availableAccounts.addAll(accounts); _nextIndex += limit; - } catch (e) { + } on Exception catch (e) { printV(e); error = hardwareWalletVM.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error; diff --git a/pubspec_overrides.yaml b/pubspec_overrides.yaml index 90cceec37..15910a4f2 100644 --- a/pubspec_overrides.yaml +++ b/pubspec_overrides.yaml @@ -1480,13 +1480,13 @@ dependency_overrides: ref: d1242cea90f84b00200e7bcab914a5af750e23fb trezor_flutter: git: - url: https://github.com/cake-tech/trezor-flutter.git - ref: 7d27e19b7bcdcd6241df35953f9a6ec2df0c7a97 + url: https://github.com/cake-tech/trezor-flutter + ref: f5121b1f3a06000ff836da8e6e6c83f53a9fa487 path: ./trezor-flutter trezor_usb_transport: git: - url: https://github.com/cake-tech/trezor-flutter.git - ref: 7d27e19b7bcdcd6241df35953f9a6ec2df0c7a97 + url: https://github.com/cake-tech/trezor-flutter + ref: f5121b1f3a06000ff836da8e6e6c83f53a9fa487 path: ./trezor_usb_transport tuple: git: diff --git a/tool/configure.dart b/tool/configure.dart index 2f170465c..e30bb6e9d 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -127,6 +127,7 @@ import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox; import 'package:trezor_connect/trezor_connect.dart' as trezor; +import 'package:trezor_flutter/trezor_flutter.dart' as trezor; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:collection/collection.dart'; @@ -156,6 +157,7 @@ import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart'; import 'package:cw_bitcoin/hardware/litecoin_ledger_service.dart'; import 'package:cw_bitcoin/hardware/bitbox_service.dart'; +import 'package:cw_bitcoin/hardware/litecoin_trezor_service.dart'; import 'package:cw_bitcoin/hardware/trezor_service.dart'; import 'package:mobx/mobx.dart'; import "package:breez_sdk_spark_flutter/src/rust/errors.dart"; @@ -275,7 +277,7 @@ abstract class Bitcoin { Future setHardwareWalletService(WalletBase wallet, HardwareWalletService service); HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection, bool isBitcoin); HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager, bool isBitcoin); - HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect, bool isBitcoin); + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect? connect, trezor.TrezorClient? client, bool isBitcoin); List updateOutputs(PendingTransaction pendingTransaction, List outputs); bool txIsReceivedSilentPayment(TransactionInfo txInfo); bool txIsMweb(TransactionInfo txInfo);