-
Notifications
You must be signed in to change notification settings - Fork 272
LCW-1128-Add-support-for-Trezor #2565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
c15ca8d
5b3e608
6aa80f2
495e058
f333d63
d7cbdca
739ddd5
3793e15
c5c31fa
0dbb44e
33707da
8d0a68d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,17 @@ | ||
| import 'dart:typed_data'; | ||
|
|
||
| import 'package:cw_bitcoin/electrum_wallet.dart'; | ||
| import 'package:bitcoin_base/bitcoin_base.dart'; | ||
| import 'package:ledger_litecoin/ledger_litecoin.dart'; | ||
|
|
||
| mixin BitcoinHardwareWalletService { | ||
| Future<Uint8List> getMasterFingerprint(); | ||
| } | ||
|
|
||
| mixin LitecoinHardwareWalletService { | ||
| Future<String> signLitecoinTransaction({ | ||
| required List<BitcoinBaseOutput> outputs, | ||
| required List<LedgerTransaction> inputs, | ||
| required Map<String, PublicKeyWithDerivationPath> publicKeys, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| 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/utils.dart'; | ||
| import 'package:cw_core/hardware/hardware_account_data.dart'; | ||
| import 'package:cw_core/hardware/hardware_wallet_service.dart'; | ||
| import 'package:ledger_bitcoin/psbt.dart'; | ||
| import 'package:ledger_litecoin/src/tx_utils/transaction.dart'; | ||
| import 'package:trezor_connect/trezor_connect.dart'; | ||
|
|
||
| class BitcoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService { | ||
| BitcoinTrezorService(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>[]; | ||
|
|
||
| for (final i in indexRange) { | ||
| requestParams.add(TrezorGetPublicKeyParams(path: "m/84'/0'/$i'")); | ||
| } | ||
|
|
||
| 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() ?? | ||
| []; | ||
| } | ||
|
|
||
| @override | ||
| Future<Uint8List> signTransaction({required String transaction}) async { | ||
| final psbt = PsbtV2()..deserialize(base64Decode(transaction)); | ||
|
|
||
| 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")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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, | ||
| sequence: psbt.getInputSequence(i), | ||
| scriptType: "SPENDWITNESS")); | ||
| } | ||
|
|
||
| final outputs = <TrezorTxOutput>[]; | ||
| final outputCount = psbt.getGlobalOutputCount(); | ||
| for (var i = 0; i < outputCount; i++) { | ||
| final script = Script.fromRaw(byteData: psbt.getOutputScript(i)); | ||
| outputs.add(TrezorTxOutput( | ||
| amount: psbt.getOutputAmount(i), | ||
| address: script.toAddress(), | ||
| scriptType: _getScriptType(script.getAddressType()!) | ||
| // 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); | ||
| } | ||
|
|
||
| @override | ||
| Future<Uint8List> getMasterFingerprint() async => Uint8List.fromList([0, 0, 0, 0]); | ||
| } | ||
|
|
||
| class LitecoinTrezorService extends HardwareWalletService with 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<BitcoinBaseOutput> outputs, | ||
| required List<LedgerTransaction> inputs, | ||
| required Map<String, PublicKeyWithDerivationPath> publicKeys}) async { | ||
| final readyInputs = <TrezorTxInput>[]; | ||
| for (final input in inputs) { | ||
| final inputTx = BtcTransaction.fromRaw(input.rawTx); | ||
| final inputOutputIndex = input.outputIndex; | ||
|
|
||
| readyInputs.add(TrezorTxInput( | ||
| prevHash: inputTx.txId(), | ||
| prevIndex: inputOutputIndex, | ||
| amount: inputTx.outputs[inputOutputIndex].amount.toInt(), | ||
| addressPath: Bip32PathParser.parse(input.ownerDerivationPath).toList(), | ||
| sequence: input.sequence, | ||
| scriptType: "SPENDWITNESS", | ||
| )); | ||
| } | ||
|
|
||
| final readyOutputs = <TrezorTxOutput>[]; | ||
| for (final output in outputs) { | ||
| final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; | ||
|
|
||
| readyOutputs.add(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, | ||
| )); | ||
| } | ||
|
|
||
| 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"); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.