diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 947f6dd81..306eb8282 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -113,15 +113,13 @@ jobs: cd /opt/android/cake_wallet git clone https://github.com/ltcmweb/mwebd cd /opt/android/cake_wallet/mwebd - git reset --hard 49c42597ce5036fe1065200c3c056d0aba5f1a58 + git reset --hard 7f31c84eeb2e954f2c5f385b39db3b8e3b6389e3 gomobile bind -target=android -androidapi 21 . mkdir -p /opt/android/cake_wallet/cw_mweb/android/libs/ mv ./mwebd.aar $_ cd .. rm -rf mwebd - - - name: Generate KeyStore run: | cd /opt/android/cake_wallet/android/app diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 2cf0c7331..9faac2533 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -99,7 +99,7 @@ jobs: go install golang.org/x/mobile/cmd/gomobile@latest gomobile init - - name: Build mwebd TODO for linux! + - name: Build mwebd run: | # paths are reset after each step, so we need to set them again: export PATH=$PATH:/usr/local/go/bin @@ -108,6 +108,7 @@ jobs: cd /opt/android/cake_wallet git clone https://github.com/ltcmweb/mwebd cd /opt/android/cake_wallet/mwebd + git reset --hard 7f31c84eeb2e954f2c5f385b39db3b8e3b6389e3 gomobile bind -target=android -androidapi 21 . mkdir -p /opt/android/cake_wallet/cw_mweb/android/libs/ mv ./mwebd.aar $_ diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 521384e53..ea99c6cd8 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; @@ -795,7 +796,10 @@ abstract class ElectrumWalletBase throw BitcoinTransactionWrongBalanceException(); } - final changeAddress = await walletAddresses.getChangeAddress(); + final changeAddress = await walletAddresses.getChangeAddress( + outputs: outputs, + utxoDetails: utxoDetails, + ); final address = addressTypeFromStr(changeAddress, network); outputs.add(BitcoinOutput( address: address, @@ -2061,7 +2065,8 @@ abstract class ElectrumWalletBase _isTryingToConnect = true; Timer(Duration(seconds: 10), () { - if (this.syncStatus is NotConnectedSyncStatus || this.syncStatus is LostConnectionSyncStatus) { + if (this.syncStatus is NotConnectedSyncStatus || + this.syncStatus is LostConnectionSyncStatus) { this.electrumClient.connectToUri( node!.uri, useSSL: node!.useSSL ?? false, @@ -2387,6 +2392,8 @@ class PublicKeyWithDerivationPath { } BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) { + // print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + // print(network); if (network is BitcoinCashNetwork) { if (!address.startsWith("bitcoincash:") && (address.startsWith("q") || address.startsWith("p"))) { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 388b3d468..e442a03e8 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,6 +1,7 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -239,7 +240,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } @action - Future<String> getChangeAddress() async { + Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async { updateChangeAddresses(); if (changeAddresses.isEmpty) { diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index 7db9d7bae..dcdf4ca1b 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -1,6 +1,10 @@ -import 'package:convert/convert.dart'; +import 'dart:typed_data'; + +import 'package:bech32/bech32.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/bech32/bech32_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; @@ -8,8 +12,16 @@ import 'package:cw_mweb/cw_mweb.dart'; import 'package:cw_mweb/mwebd.pb.dart'; import 'package:mobx/mobx.dart'; +// import 'dart:typed_data'; +// import 'package:bech32/bech32.dart'; +// import 'package:r_crypto/r_crypto.dart'; + part 'litecoin_wallet_addresses.g.dart'; +String encodeMwebAddress(List<int> scriptPubKey) { + return bech32.encode(Bech32("ltcmweb1", scriptPubKey), 250); +} + class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses; abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store { @@ -42,18 +54,15 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with List<String> mwebAddrs = []; Future<void> topUpMweb(int index) async { - final stub = await CwMweb.stub(); + // generate up to index + 1000 addresses: while (mwebAddrs.length - index < 1000) { final length = mwebAddrs.length; - final resp = await stub.addresses(AddressRequest( - fromIndex: length, - toIndex: index + 1000, - scanSecret: scanSecret, - spendPubkey: spendPubkey, - )); - if (mwebAddrs.length == length) { - mwebAddrs.addAll(resp.address); - } + final address = await CwMweb.address( + Uint8List.fromList(scanSecret), + Uint8List.fromList(spendPubkey), + length, + ); + mwebAddrs.add(address!); } } @@ -63,7 +72,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with required Bip32Slip10Secp256k1 hd, BitcoinAddressType? addressType, }) { - if (addressType == SegwitAddresType.mweb && mwebEnabled) { + if (addressType == SegwitAddresType.mweb) { topUpMweb(index); return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1]; } @@ -76,11 +85,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with required Bip32Slip10Secp256k1 hd, BitcoinAddressType? addressType, }) async { - // if mweb isn't enabled we'll just return the regular address type which does effectively nothing - // sort of a hack but easier than trying to pull the mweb setting into the electrum_wallet_addresses initialization code - // (we want to avoid initializing the mweb.stub() if it's not enabled or we'd be starting the whole server for no reason and it's slow) - // TODO: find a way to do address generation without starting the whole mweb server - if (addressType == SegwitAddresType.mweb && mwebEnabled) { + if (addressType == SegwitAddresType.mweb) { await topUpMweb(index); } return getAddress(index: index, hd: hd, addressType: addressType); @@ -88,11 +93,38 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with @action @override - Future<String> getChangeAddress() async { + Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async { + // use regular change address on peg in, otherwise use mweb for change address: + + if (outputs != null && utxoDetails != null) { + // check if this is a PEGIN: + bool outputsToMweb = false; + bool comesFromMweb = false; + + for (var i = 0; i < outputs.length; i++) { + // TODO: probably not the best way to tell if this is an mweb address + // (but it doesn't contain the "mweb" text at this stage) + if (outputs[i].address.toAddress(network).length > 110) { + outputsToMweb = true; + } + } + utxoDetails.availableInputs.forEach((element) { + if (element.address.contains("mweb")) { + comesFromMweb = true; + } + }); + + bool isPegIn = !comesFromMweb && outputsToMweb; + if (isPegIn && mwebEnabled) { + return super.getChangeAddress(); + } + } + if (mwebEnabled) { await topUpMweb(0); return mwebAddrs[0]; } + return super.getChangeAddress(); } } diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 29d178319..8588969c4 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -41,6 +41,9 @@ dependencies: git: url: https://github.com/rafael-xmr/sp_scanner ref: sp_v4.0.0 + bech32: + git: + url: https://github.com/cake-tech/bech32.git dev_dependencies: flutter_test: @@ -62,6 +65,7 @@ dependency_overrides: url: https://github.com/cake-tech/bitcoin_base ref: cake-update-v6 pointycastle: 3.7.4 + ffi: 2.1.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt index b1180dd4a..57ae3d4c3 100644 --- a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt +++ b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt @@ -30,8 +30,6 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler { if (call.method == "start") { server?.stop() val dataDir = call.argument("dataDir") ?: "" - // server = server ?: Mwebd.newServer("", dataDir, "") - // port = port ?: server?.start(0) server = server ?: Mwebd.newServer("", dataDir, "") port = server?.start(0) result.success(port) @@ -40,6 +38,12 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler { server = null port = null result.success(null) + } else if (call.method == "address") { + val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0) + val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0) + val index: Int = call.argument<Int>("index") ?: 0 + val res = Mwebd.address(scanSecret, spendPub, index) + result.success(res) } else { result.notImplemented() } diff --git a/cw_mweb/ios/Classes/CwMwebPlugin.swift b/cw_mweb/ios/Classes/CwMwebPlugin.swift index ed08d6748..f1fd78cd8 100644 --- a/cw_mweb/ios/Classes/CwMwebPlugin.swift +++ b/cw_mweb/ios/Classes/CwMwebPlugin.swift @@ -3,70 +3,84 @@ import UIKit import Mwebd public class CwMwebPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "cw_mweb", binaryMessenger: registrar.messenger()) - let instance = CwMwebPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } +public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "cw_mweb", binaryMessenger: registrar.messenger()) + let instance = CwMwebPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } - private static var server: MwebdServer? - private static var port: Int = 0 - private static var dataDir: String? - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("iOS " + UIDevice.current.systemVersion) - case "start": - stopServer() - let args = call.arguments as? [String: String] - let dataDir = args?["dataDir"] - CwMwebPlugin.dataDir = dataDir - startServer(result: result) - case "stop": - stopServer() - result(nil) - default: - result(FlutterMethodNotImplemented) - } - } + private static var server: MwebdServer? + private static var port: Int = 0 + private static var dataDir: String? - private func startServer(result: @escaping FlutterResult) { - if CwMwebPlugin.server == nil { - var error: NSError? - CwMwebPlugin.server = MwebdNewServer("", CwMwebPlugin.dataDir, "", &error) - - if let server = CwMwebPlugin.server { - do { - print("Starting server...") - try server.start(0, ret0_: &CwMwebPlugin.port) - print("Server started successfully on port: \(CwMwebPlugin.port)") - result(CwMwebPlugin.port) - } catch let startError as NSError { - print("Server Start Error: \(startError.localizedDescription)") - result(FlutterError(code: "Server Start Error", message: startError.localizedDescription, details: nil)) - } - } else if let error = error { - print("Server Creation Error: \(error.localizedDescription)") - result(FlutterError(code: "Server Creation Error", message: error.localizedDescription, details: nil)) - } else { - print("Unknown Error: Failed to create server") - result(FlutterError(code: "Unknown Error", message: "Failed to create server", details: nil)) - } - } else { - print("Server already running on port: \(CwMwebPlugin.port)") - result(CwMwebPlugin.port) - } - } + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("iOS " + UIDevice.current.systemVersion) + break + case "start": + stopServer() + let args = call.arguments as? [String: String] + let dataDir = args?["dataDir"] + CwMwebPlugin.dataDir = dataDir + startServer(result: result) + break + case "stop": + stopServer() + result(nil) + break + case "address": + let args = call.arguments as! [String: Any] + let scanSecret = args["scanSecret"] as! FlutterStandardTypedData + let spendPub = args["spendPub"] as! FlutterStandardTypedData + let index = args["index"] as! Int32 + + let scanSecretData = scanSecret.data + let spendPubData = spendPub.data + result(MwebdAddress(scanSecretData, spendPubData, index)) + break + default: + result(FlutterMethodNotImplemented) + break + } + } - private func stopServer() { - print("Stopping server") - CwMwebPlugin.server?.stop() - CwMwebPlugin.server = nil - CwMwebPlugin.port = 0 - } + private func startServer(result: @escaping FlutterResult) { + if CwMwebPlugin.server == nil { + var error: NSError? + CwMwebPlugin.server = MwebdNewServer("", CwMwebPlugin.dataDir, "", &error) - deinit { - stopServer() - } -} \ No newline at end of file + if let server = CwMwebPlugin.server { + do { + print("Starting server...") + try server.start(0, ret0_: &CwMwebPlugin.port) + print("Server started successfully on port: \(CwMwebPlugin.port)") + result(CwMwebPlugin.port) + } catch let startError as NSError { + print("Server Start Error: \(startError.localizedDescription)") + result(FlutterError(code: "Server Start Error", message: startError.localizedDescription, details: nil)) + } + } else if let error = error { + print("Server Creation Error: \(error.localizedDescription)") + result(FlutterError(code: "Server Creation Error", message: error.localizedDescription, details: nil)) + } else { + print("Unknown Error: Failed to create server") + result(FlutterError(code: "Unknown Error", message: "Failed to create server", details: nil)) + } + } else { + print("Server already running on port: \(CwMwebPlugin.port)") + result(CwMwebPlugin.port) + } + } + + private func stopServer() { + print("Stopping server") + CwMwebPlugin.server?.stop() + CwMwebPlugin.server = nil + CwMwebPlugin.port = 0 + } + + deinit { + stopServer() + } +} diff --git a/cw_mweb/lib/cw_mweb.dart b/cw_mweb/lib/cw_mweb.dart index df3300379..e71ddde97 100644 --- a/cw_mweb/lib/cw_mweb.dart +++ b/cw_mweb/lib/cw_mweb.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:grpc/grpc.dart'; import 'package:path_provider/path_provider.dart'; @@ -53,6 +54,15 @@ class CwMweb { await cleanup(); } + static Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async { + // try { + // return (await CwMwebPlatform.instance.address(scan, spendPub, index))!; + // } catch (e) { + // print("error generating address!: $e"); + // } + return CwMwebPlatform.instance.address(scanSecret, spendPub, index); + } + static Future<void> cleanup() async { await _clientChannel?.terminate(); _rpcClient = null; diff --git a/cw_mweb/lib/cw_mweb_method_channel.dart b/cw_mweb/lib/cw_mweb_method_channel.dart index 9451db310..70e4a1789 100644 --- a/cw_mweb/lib/cw_mweb_method_channel.dart +++ b/cw_mweb/lib/cw_mweb_method_channel.dart @@ -19,4 +19,14 @@ class MethodChannelCwMweb extends CwMwebPlatform { Future<void> stop() async { await methodChannel.invokeMethod<void>('stop'); } + + @override + Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async { + final result = await methodChannel.invokeMethod<String>('address', { + 'scanSecret': scanSecret, + 'spendPub': spendPub, + 'index': index, + }); + return result; + } } diff --git a/cw_mweb/lib/cw_mweb_platform_interface.dart b/cw_mweb/lib/cw_mweb_platform_interface.dart index a5a46adbc..8cc80f3e9 100644 --- a/cw_mweb/lib/cw_mweb_platform_interface.dart +++ b/cw_mweb/lib/cw_mweb_platform_interface.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'cw_mweb_method_channel.dart'; @@ -30,4 +32,8 @@ abstract class CwMwebPlatform extends PlatformInterface { Future<void> stop() { throw UnimplementedError('stop() has not been implemented.'); } + + Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) { + throw UnimplementedError('address(int) has not been implemented.'); + } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 5498fa95e..d46f836d1 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -138,6 +138,7 @@ dependency_overrides: git: url: https://github.com/cake-tech/bitcoin_base ref: cake-update-v6 + ffi: 2.1.0 flutter_icons: image_path: "assets/images/app_logo.png" diff --git a/scripts/android/build_mwebd.sh b/scripts/android/build_mwebd.sh index d3a3bf091..269e58d8c 100755 --- a/scripts/android/build_mwebd.sh +++ b/scripts/android/build_mwebd.sh @@ -8,6 +8,7 @@ gomobile init # build mwebd: git clone https://github.com/ltcmweb/mwebd cd mwebd +git reset --hard 7f31c84eeb2e954f2c5f385b39db3b8e3b6389e3 gomobile bind -target=android -androidapi 21 . mkdir -p ../../../cw_mweb/android/libs/ mv ./mwebd.aar $_ diff --git a/scripts/ios/build_mwebd.sh b/scripts/ios/build_mwebd.sh index 08dbd7cd0..ae1726f23 100755 --- a/scripts/ios/build_mwebd.sh +++ b/scripts/ios/build_mwebd.sh @@ -7,6 +7,7 @@ gomobile init # build mwebd: git clone https://github.com/ltcmweb/mwebd cd mwebd +git reset --hard 7f31c84eeb2e954f2c5f385b39db3b8e3b6389e3 gomobile bind -target=ios . mv -fn ./Mwebd.xcframework ../../../ios/ # cleanup: