feat(cw_bitcoin): support socks proxy and CakeTor

This commit is contained in:
Czarek Nakamoto 2025-04-25 13:10:17 +02:00
parent 37cbfb949a
commit 22e5b39cb8
16 changed files with 288 additions and 55 deletions

View file

@ -5,6 +5,8 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
@ -42,7 +44,7 @@ class ElectrumClient {
static const aliveTimerDuration = Duration(seconds: 4);
bool get isConnected => _isConnected;
Socket? socket;
ProxySocket? socket;
void Function(ConnectionStatus)? onConnectionStatusChange;
int _id;
final Map<String, SocketTask> _tasks;
@ -73,17 +75,9 @@ class ElectrumClient {
socket = null;
try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
socket = await Socket.connect(host, port, timeout: connectionTimeout);
} else {
socket = await SecureSocket.connect(
host,
port,
timeout: connectionTimeout,
onBadCertificate: (_) => true,
);
}
socket = await ProxyWrapper().getSocksSocket(useSSL ?? false, host, port, connectionTimeout: connectionTimeout);
} catch (e) {
printV("connect: $e");
if (e is HandshakeException) {
useSSL = !(useSSL ?? false);
}

View file

@ -902,11 +902,21 @@ packages:
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
path: "."
ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: "direct main"
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
@ -992,7 +1002,7 @@ packages:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"

View file

@ -49,6 +49,10 @@ dependencies:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-litecoin
socks_socket:
git:
url: https://github.com/sneurlax/socks_socket
ref: e6232c53c1595469931ababa878759a067c02e94
dev_dependencies:
flutter_test:

View file

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:cw_core/keyable.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'dart:convert';
@ -303,13 +304,9 @@ class Node extends HiveObject with Keyable {
// you try to communicate with it
Future<bool> requestElectrumServer() async {
try {
final Socket socket;
if (useSSL == true) {
socket = await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
} else {
socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
}
final ProxySocket socket;
socket = await ProxyWrapper().getSocksSocket(useSSL ?? false, uri.host, uri.port);
socket.destroy();
return true;

View file

@ -0,0 +1,47 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_socket/insecure.dart';
import 'package:cw_core/utils/proxy_socket/secure.dart';
import 'package:cw_core/utils/proxy_socket/socks.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:socks_socket/socks_socket.dart';
class ProxyAddress {
final String host;
final int port;
ProxyAddress({required this.host, required this.port});
}
abstract class ProxySocket {
static Future<ProxySocket> connect(bool sslEnabled, ProxyAddress address, {Duration? connectionTimeout}) async {
if (CakeTor.instance.started) {
var socksSocket = await SOCKSSocket.create(
proxyHost: InternetAddress.loopbackIPv4.address,
proxyPort: CakeTor.instance.port,
sslEnabled: sslEnabled,
);
await socksSocket.connect();
await socksSocket.connectTo(address.host, address.port);
return ProxySocketSocks(socksSocket);
}
if (sslEnabled == false) {
return ProxySocketInsecure(await Socket.connect(address.host, address.port, timeout: connectionTimeout));
} else {
return ProxySocketSecure(await SecureSocket.connect(
address.host,
address.port,
timeout: connectionTimeout,
onBadCertificate: (_) => true,
));
}
}
Future<void> close();
Future<void> destroy();
Future<void> write(String data);
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function (Object error)? onError, Function ()? onDone, bool cancelOnError = true});
ProxyAddress get address;
}

View file

@ -0,0 +1,34 @@
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'dart:async';
import 'dart:typed_data';
import 'dart:io';
class ProxySocketInsecure implements ProxySocket {
final Socket socket;
ProxySocketInsecure(this.socket);
ProxyAddress get address => ProxyAddress(host: socket.remoteAddress.host, port: socket.remotePort);
@override
Future<void> close() => socket.close();
@override
Future<void> destroy() async => socket.destroy();
@override
Future<void> write(String data) async => socket.write(data);
@override
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) {
return socket.listen(
(data) {
onData(Uint8List.fromList(data));
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
}

View file

@ -0,0 +1,34 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
class ProxySocketSecure implements ProxySocket {
final SecureSocket socket;
ProxySocketSecure(this.socket);
ProxyAddress get address => ProxyAddress(host: socket.remoteAddress.host, port: socket.remotePort);
@override
Future<void> close() => socket.close();
@override
Future<void> destroy() async => socket.destroy();
@override
Future<void> write(String data) async => socket.write(data);
@override
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) {
return socket.listen(
(data) {
onData(Uint8List.fromList(data));
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
}

View file

@ -0,0 +1,36 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:socks_socket/socks_socket.dart';
class ProxySocketSocks implements ProxySocket {
final SOCKSSocket socket;
ProxySocketSocks(this.socket);
@override
ProxyAddress get address => ProxyAddress(host: socket.proxyHost, port: socket.proxyPort);
@override
Future<void> close() => socket.close();
@override
Future<void> destroy() => close();
@override
Future<void> write(String data) async => socket.write(data);
@override
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) {
return socket.listen(
(data) {
onData(Uint8List.fromList(data));
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
}

View file

@ -1,14 +1,20 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:socks5_proxy/socks_client.dart';
import 'package:socks_socket/socks_socket.dart';
import 'package:tor/tor.dart';
import 'package:http/io_client.dart' as ioc;
class ProxyWrapper {
ProxyWrapper();
Future<ProxySocket> getSocksSocket(bool sslEnabled, String host, int port, {Duration? connectionTimeout}) async {
return ProxySocket.connect(sslEnabled, ProxyAddress(host: host, port: port), connectionTimeout: connectionTimeout);
}
ioc.IOClient getHttpIOClient() {
final httpClient = ProxyWrapper().getHttpClient();
return ioc.IOClient(httpClient);

View file

@ -641,6 +641,15 @@ packages:
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: "direct main"
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
@ -717,7 +726,7 @@ packages:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"

View file

@ -35,6 +35,10 @@ dependencies:
url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v2
tor: any
socks_socket:
git:
url: https://github.com/sneurlax/socks_socket
ref: e6232c53c1595469931ababa878759a067c02e94
dev_dependencies:
flutter_test:

View file

@ -666,11 +666,21 @@ packages:
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
path: "."
ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
@ -751,6 +761,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.2"
tor:
dependency: transitive
description:
name: tor
sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142
url: "https://pub.dev"
source: hosted
version: "0.0.8"
tuple:
dependency: transitive
description:

View file

@ -779,11 +779,21 @@ packages:
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
path: "."
ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
@ -860,7 +870,7 @@ packages:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"

View file

@ -771,11 +771,21 @@ packages:
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
path: "."
ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
@ -852,7 +862,7 @@ packages:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"

View file

@ -670,11 +670,21 @@ packages:
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
path: "."
ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
@ -751,7 +761,7 @@ packages:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"

View file

@ -667,11 +667,21 @@ packages:
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
path: "."
ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description: