feat: enhance hardware wallet compatibility and improve transaction handling (#3517)
What changed, and why it matters
This commit refactors how Cake Wallet builds Bitcoin and Litecoin transactions for hardware wallets (Ledger, Trezor, BitBox). It mainly improves change-output handling, derivation-path reporting, and adds a new Litecoin Trezor signing path. There is no explicit security bug fix in the commit message, but the changes touch sensitive transaction-assembly code where mistakes could affect how much money is sent or which addresses are shown on the device screen.
Treat this as a high-sensitivity functional refactor rather than a confirmed vulnerability. Review the new `PSBTReadyBitcoinOutput` change-output logic for off-by-one or silent-payment edge cases, verify the Trezor fingerprint check cannot be bypassed by a missing-derivation PSBT, and run hardware-wallet signing tests on Bitcoin and Litecoin with both change and payment outputs before release.
Security signals we found
Change-output derivation metadata now embedded in PSBT outputs, reducing risk of hardware wallet mis-classifying change as a payment
Bitcoin Trezor signing now validates that PSBT input fingerprints match the device master fingerprint before signing
Bitcoin Trezor signing now includes full previous-transaction metadata for inputs instead of only summary data
Exception handling narrowed from catch-all to Exception, which may leave Errors unhandled but also avoids swallowing runtime errors silently
Large refactor of hardware-wallet transaction signing paths with new native SDK dependency
Evidence from the diff
The patch updates PSBT construction so change outputs carry their BIP32 derivation path, master fingerprint and public key, and removes the old cwOutputs parameter from PSBTTransactionBuild. Bitcoin Trezor signing is rewritten to use a new native trezor_flutter SDK, including fingerprint validation against PSBT derivations and explicit prev-tx metadata. Litecoin gets a dedicated LitecoinTrezorService. Exception handling in wallet_hardware_restore_view_model is narrowed from catch (e) to on Exception catch (e). No CVE, advisory, or vendor security disclosure is present in the supplied materials.
Changed components
cw_bitcoin/lib/bitcoin_wallet.dartcw_bitcoin/lib/electrum_wallet.dartcw_bitcoin/lib/litecoin_wallet.dartcw_bitcoin/lib/psbt/transaction_builder.dartcw_bitcoin/lib/hardware/trezor_service.dartcw_bitcoin/lib/hardware/litecoin_trezor_service.dartcw_bitcoin/lib/hardware/litecoin_ledger_service.dartcw_bitcoin/lib/hardware/bitbox_service.dartlib/view_model/hardware_wallet/trezor_connect_view_model.dartlib/view_model/wallet_hardware_restore_view_model.dartInspect captured patch +526 / −310
diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart
index ac40de0..a583dcc 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<PsbtV2> buildPsbt({
- required List<BitcoinBaseOutput> outputs,
+ required List<BitcoinOutput> outputs,
required List<OutputInfo> 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<BtcTransaction> buildHardwareWalletTransaction({
- required List<BitcoinBaseOutput> outputs,
+ required List<BitcoinOutput> outputs,
required BigInt fee,
required BasedUtxoNetwork network,
required List<UtxoWithAddress> utxos,
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 1da0169..8b1cc99 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<BtcTransaction> buildHardwareWalletTransaction({
- required List<BitcoinBaseOutput> outputs,
+ required List<BitcoinOutput> outputs,
required BigInt fee,
required BasedUtxoNetwork network,
required List<UtxoWithAddress> utxos,
diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart
index a71dcac..4b3ea59 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<String> signLitecoinTransaction({
- required List<BitcoinBaseOutput> outputs,
+ required List<PSBTReadyBitcoinOutput> outputs,
required List<PSBTReadyUtxoWithAddress> inputs,
required Map<String, PublicKeyWithDerivationPath> 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 885868d..22954d1 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<Uint8List> getMasterFingerprint() async => Uint8List.fromList([0, 0, 0, 0]);
@@ -10,7 +9,7 @@ mixin BitcoinHardwareWalletService {
mixin LitecoinHardwareWalletService on BitcoinHardwareWalletService {
Future<String> signLitecoinTransaction({
- required List<BitcoinBaseOutput> outputs,
+ required List<PSBTReadyBitcoinOutput> outputs,
required List<PSBTReadyUtxoWithAddress> inputs,
required Map<String, PublicKeyWithDerivationPath> publicKeys,
});
diff --git a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
index aa73fc1..852f778 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<String> signLitecoinTransaction({
- required List<BitcoinBaseOutput> outputs,
+ required List<PSBTReadyBitcoinOutput> outputs,
required List<PSBTReadyUtxoWithAddress> inputs,
required Map<String, PublicKeyWithDerivationPath> 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 0000000..4858e30
--- /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<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final indexRange = List.generate(limit, (i) => i + index);
+ final requestParams = <TrezorGetPublicKeyParams>[];
+ 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<String> signLitecoinTransaction({
+ required List<PSBTReadyBitcoinOutput> outputs,
+ required List<PSBTReadyUtxoWithAddress> inputs,
+ required Map<String, PublicKeyWithDerivationPath> 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 c9071cc..238a01e 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<Uint8List> 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<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
- final requestParams = <TrezorGetPublicKeyParams>[];
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<Uint8List> signTransaction({required String transaction}) async {
final psbt = PsbtV2()..deserialize(base64Decode(transaction));
+ final masterFingerprint = await getMasterFingerprint();
+
+ final inputs = <sdk.TrezorTxInput>[];
+ final prevTxs = <String, sdk.TrezorPrevTx>{};
- final inputs = <TrezorTxInput>[];
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 publicKeys = psbt.inputMaps[i].keys.where((e) => e.startsWith("06"));
- final pubkey = Uint8List.fromList(hex.decode(publicKeys.first.substring(2)));
-
- 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 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 txidBytes = psbt.getInputPreviousTxid(i).reversed.toList();
+
+ 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 = <TrezorTxOutput>[];
+ final outputs = <sdk.TrezorTxOutput>[];
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);
-
- return Uint8List.fromList(BytesUtils.fromHexString(signedTx!.serializedTx));
- }
-
- @override
- Future<Uint8List> 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<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
- final indexRange = List.generate(limit, (i) => i + index);
- final requestParams = <TrezorGetPublicKeyParams>[];
- final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
-
- for (final i in indexRange) {
- final derivationPath = "m/84'/2'/$i'";
- requestParams.add(TrezorGetPublicKeyParams(path: derivationPath, coin: "LTC"));
- }
+ final signed = await _trezorBitcoin.signTransaction(
+ inputs: inputs,
+ outputs: outputs,
+ prevTxs: prevTxs,
+ version: psbt.getGlobalTxVersion(),
+ lockTime: psbt.getGlobalFallbackLocktime() ?? 0,
+ );
- 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() ??
- [];
+ return signed.serializedTx;
}
@override
- Future<String> signLitecoinTransaction({
- required List<BitcoinBaseOutput> outputs,
- required List<PSBTReadyUtxoWithAddress> inputs,
- required Map<String, PublicKeyWithDerivationPath> 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<Uint8List> 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<String, Uint8List> 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 aa8fcd3..fbd9462 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<BtcTransaction> buildHardwareWalletTransaction({
- required List<BitcoinBaseOutput> outputs,
+ required List<BitcoinOutput> outputs,
required BigInt fee,
required BasedUtxoNetwork network,
required List<UtxoWithAddress> 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 = <PSBTReadyUtxoWithAddress>[];
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 ab107bf..5eea757 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<PSBTReadyUtxoWithAddress> inputs,
- required List<BitcoinBaseOutput> outputs,
- required List<OutputInfo> cwOutputs,
- bool enableRBF = true,
- int locktime = 0}) {
+ PSBTTransactionBuild({
+ required List<PSBTReadyUtxoWithAddress> inputs,
+ required List<PSBTReadyBitcoinOutput> 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);
-
- if (bip353Name.length > 255) {
- printV('BIP353 name is too long, skipping');
- continue;
- }
- final proof = Uint8List.fromList([
- bip353Name.length,
- ...bip353Name,
- ...bip353Proof,
- ]);
-
- psbt.setOutputDNSSECProof(i, proof);
+ psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes()));
+ psbt.setOutputAmount(i, output.value.toInt());
+
+ 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(),
+ );
+ }
+
+ 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.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
- Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
+ 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()),
+ );
}
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 3eb747a..922a83f 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 04d3f39..4e31969 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 c2343df..4e4789e 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 2b01b07..643a03c 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -361,9 +361,10 @@ Route<dynamic> 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<HardwareWalletViewModel>(param1: hardwareWalletType),
@@ -373,19 +374,23 @@ Route<dynamic> createRoute(RouteSettings settings) {
return handleRouteWithPlatformAwareness(
(_) => getIt.get<NewWalletTypePage>(
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 4d65e4d..6e23881 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 35de089..4d7a483 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 90cceec..15910a4 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 2f17046..e30bb6e 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<void> 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<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
bool txIsReceivedSilentPayment(TransactionInfo txInfo);
bool txIsMweb(TransactionInfo txInfo);
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.