diff --git a/lib/services/cashfusion/comms.dart b/lib/services/cashfusion/comms.dart index dd623459b..e219ff38f 100644 --- a/lib/services/cashfusion/comms.dart +++ b/lib/services/cashfusion/comms.dart @@ -1,12 +1,12 @@ - -import 'util.dart'; -import 'connection.dart'; -import 'fusion.dart'; -import 'fusion.pb.dart'; -import 'dart:io'; import 'dart:async'; +import 'dart:io'; + import 'package:protobuf/protobuf.dart'; -import 'socketwrapper.dart'; +import 'package:stackwallet/services/cashfusion/connection.dart'; +import 'package:stackwallet/services/cashfusion/fusion.dart'; +import 'package:stackwallet/services/cashfusion/fusion.pb.dart'; +import 'package:stackwallet/services/cashfusion/socketwrapper.dart'; +import 'package:stackwallet/services/cashfusion/util.dart'; typedef PbCreateFunc = GeneratedMessage Function(); @@ -23,33 +23,31 @@ Map pbClassCreators = { ServerHello: () => ServerHello(), JoinPools: () => JoinPools(), TierStatusUpdate: () => TierStatusUpdate(), - FusionBegin: ()=> FusionBegin(), - StartRound: ()=> StartRound(), - PlayerCommit: ()=> PlayerCommit(), - BlindSigResponses: ()=> BlindSigResponses(), - AllCommitments: ()=> AllCommitments(), - CovertComponent: ()=> CovertComponent(), - ShareCovertComponents: ()=> ShareCovertComponents(), - CovertTransactionSignature: ()=> CovertTransactionSignature(), - FusionResult: ()=> FusionResult(), - MyProofsList: ()=> MyProofsList(), - TheirProofsList: ()=> TheirProofsList(), - Blames: ()=> Blames(), - RestartRound: ()=> RestartRound(), - Error: ()=> Error(), - Ping: ()=> Ping(), - OK: ()=> OK(), - ClientMessage: ()=> ClientMessage(), - ServerMessage: ()=> ServerMessage(), - CovertMessage: ()=> CovertMessage(), - CovertResponse: ()=> CovertResponse() - + FusionBegin: () => FusionBegin(), + StartRound: () => StartRound(), + PlayerCommit: () => PlayerCommit(), + BlindSigResponses: () => BlindSigResponses(), + AllCommitments: () => AllCommitments(), + CovertComponent: () => CovertComponent(), + ShareCovertComponents: () => ShareCovertComponents(), + CovertTransactionSignature: () => CovertTransactionSignature(), + FusionResult: () => FusionResult(), + MyProofsList: () => MyProofsList(), + TheirProofsList: () => TheirProofsList(), + Blames: () => Blames(), + RestartRound: () => RestartRound(), + Error: () => Error(), + Ping: () => Ping(), + OK: () => OK(), + ClientMessage: () => ClientMessage(), + ServerMessage: () => ServerMessage(), + CovertMessage: () => CovertMessage(), + CovertResponse: () => CovertResponse() }; - - -Future sendPb(Connection connection, Type pbClass, GeneratedMessage subMsg, {Duration? timeout}) async { - +Future sendPb( + Connection connection, Type pbClass, GeneratedMessage subMsg, + {Duration? timeout}) async { // Construct the outer message with the submessage. if (pbClassCreators[pbClass] == null) { @@ -70,11 +68,9 @@ Future sendPb(Connection connection, Type pbClass, GeneratedMessage subMsg } } - - -Future sendPb2(SocketWrapper socketwrapper, Connection connection, Type pbClass, GeneratedMessage subMsg, {Duration? timeout}) async { - - +Future sendPb2(SocketWrapper socketwrapper, Connection connection, + Type pbClass, GeneratedMessage subMsg, + {Duration? timeout}) async { // Construct the outer message with the submessage. if (pbClassCreators[pbClass] == null) { @@ -85,7 +81,8 @@ Future sendPb2(SocketWrapper socketwrapper, Connection connection, Type pb var pbMessage = pbClassCreators[pbClass]!()..mergeFromMessage(subMsg); final msgBytes = pbMessage.writeToBuffer(); try { - await connection.sendMessageWithSocketWrapper(socketwrapper, msgBytes, timeout: timeout); + await connection.sendMessageWithSocketWrapper(socketwrapper, msgBytes, + timeout: timeout); } on SocketException { throw FusionError('Connection closed by remote'); } on TimeoutException { @@ -95,13 +92,12 @@ Future sendPb2(SocketWrapper socketwrapper, Connection connection, Type pb } } - - - -Future> recvPb2(SocketWrapper socketwrapper, Connection connection, Type pbClass, List expectedFieldNames, {Duration? timeout}) async { +Future> recvPb2(SocketWrapper socketwrapper, + Connection connection, Type pbClass, List expectedFieldNames, + {Duration? timeout}) async { try { - - List blob = await connection.recv_message2(socketwrapper, timeout: timeout); + List blob = + await connection.recv_message2(socketwrapper, timeout: timeout); var pbMessage = pbClassCreators[pbClass]!()..mergeFromBuffer(blob); @@ -121,8 +117,8 @@ Future> recvPb2(SocketWrapper socketwrapper, Con } } - throw FusionError('None of the expected fields found in the received message'); - + throw FusionError( + 'None of the expected fields found in the received message'); } catch (e) { // Handle different exceptions here if (e is SocketException) { @@ -134,15 +130,16 @@ Future> recvPb2(SocketWrapper socketwrapper, Con } else if (e is OSError && e.errorCode == 9) { throw FusionError('Connection closed by local'); } else { - throw FusionError('Communications error: ${e.runtimeType}: ${e.toString()}'); + throw FusionError( + 'Communications error: ${e.runtimeType}: ${e.toString()}'); } } } - -Future> recvPb(Connection connection, Type pbClass, List expectedFieldNames, {Duration? timeout}) async { +Future> recvPb( + Connection connection, Type pbClass, List expectedFieldNames, + {Duration? timeout}) async { try { - List blob = await connection.recv_message(timeout: timeout); var pbMessage = pbClassCreators[pbClass]!()..mergeFromBuffer(blob); @@ -163,8 +160,8 @@ Future> recvPb(Connection connection, Type pbCla } } - throw FusionError('None of the expected fields found in the received message'); - + throw FusionError( + 'None of the expected fields found in the received message'); } catch (e) { // Handle different exceptions here if (e is SocketException) { @@ -176,8 +173,8 @@ Future> recvPb(Connection connection, Type pbCla } else if (e is OSError && e.errorCode == 9) { throw FusionError('Connection closed by local'); } else { - throw FusionError('Communications error: ${e.runtimeType}: ${e.toString()}'); + throw FusionError( + 'Communications error: ${e.runtimeType}: ${e.toString()}'); } } } - diff --git a/lib/services/cashfusion/connection.dart b/lib/services/cashfusion/connection.dart index b9b4cc64b..64780dfba 100644 --- a/lib/services/cashfusion/connection.dart +++ b/lib/services/cashfusion/connection.dart @@ -1,10 +1,10 @@ -import 'socketwrapper.dart'; -import 'dart:io'; import 'dart:async'; -import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; -import 'package:convert/convert.dart'; + import 'package:collection/collection.dart'; +import 'package:convert/convert.dart'; +import 'package:stackwallet/services/cashfusion/socketwrapper.dart'; /* This file might need some fixing up because each time we call fillBuf, we're trying to @@ -22,14 +22,14 @@ class BadFrameError extends Error { String toString() => message; } - - -Future openConnection(String host, int port, - {double connTimeout = 5.0, - double defaultTimeout = 5.0, - bool ssl = false, - dynamic socksOpts}) async { - +Future openConnection( + String host, + int port, { + double connTimeout = 5.0, + double defaultTimeout = 5.0, + bool ssl = false, + dynamic socksOpts, +}) async { try { // Dart's Socket class handles connection timeout internally. Socket socket = await Socket.connect(host, port); @@ -38,28 +38,30 @@ Future openConnection(String host, int port, socket = await SecureSocket.secure(socket); } - return Connection(socket: socket, timeout: Duration(seconds: defaultTimeout.toInt())); - + return Connection( + socket: socket, timeout: Duration(seconds: defaultTimeout.toInt())); } catch (e) { throw 'Failed to open connection: $e'; } } - class Connection { Duration timeout = Duration(seconds: 1); Socket? socket; - static const int MAX_MSG_LENGTH = 200*1024; - static final Uint8List magic = Uint8List.fromList([0x76, 0x5b, 0xe8, 0xb4, 0xe4, 0x39, 0x6d, 0xcf]); + static const int MAX_MSG_LENGTH = 200 * 1024; + static final Uint8List magic = + Uint8List.fromList([0x76, 0x5b, 0xe8, 0xb4, 0xe4, 0x39, 0x6d, 0xcf]); final Uint8List recvbuf = Uint8List(0); Connection({required this.socket, this.timeout = const Duration(seconds: 1)}); Connection.withoutSocket({this.timeout = const Duration(seconds: 1)}); - Future sendMessageWithSocketWrapper(SocketWrapper socketwrapper, List msg, {Duration? timeout}) async { + Future sendMessageWithSocketWrapper( + SocketWrapper socketwrapper, List msg, + {Duration? timeout}) async { timeout ??= this.timeout; - print ("DEBUG sendmessage msg sending "); - print (msg); + print("DEBUG sendmessage msg sending "); + print(msg); final lengthBytes = Uint8List(4); final byteData = ByteData.view(lengthBytes.buffer); byteData.setUint32(0, msg.length, Endian.big); @@ -76,24 +78,20 @@ class Connection { } } - - Future sendMessage(List msg, {Duration? timeout}) async { - timeout ??= this.timeout; final lengthBytes = Uint8List(4); final byteData = ByteData.view(lengthBytes.buffer); byteData.setUint32(0, msg.length, Endian.big); - print (Connection.magic); + print(Connection.magic); final frame = [] ..addAll(Connection.magic) ..addAll(lengthBytes) ..addAll(msg); try { - StreamController> controller = StreamController(); controller.stream.listen((data) { @@ -109,19 +107,18 @@ class Connection { } finally { controller.close(); } - } on SocketException catch (e) { throw TimeoutException('Socket write timed out', timeout); } - } - void close() { socket?.close(); } - Future> fillBuf2(SocketWrapper socketwrapper, List recvBuf, int n, {Duration? timeout}) async { + Future> fillBuf2( + SocketWrapper socketwrapper, List recvBuf, int n, + {Duration? timeout}) async { final maxTime = timeout != null ? DateTime.now().add(timeout) : null; await for (var data in socketwrapper.socket!.cast>()) { @@ -139,7 +136,8 @@ class Connection { } recvBuf.addAll(data); - print("DEBUG fillBuf2 2 - data added to recvBuf, new length: ${recvBuf.length}"); + print( + "DEBUG fillBuf2 2 - data added to recvBuf, new length: ${recvBuf.length}"); if (recvBuf.length >= n) { print("DEBUG fillBuf2 3 - breaking loop, recvBuf is big enough"); @@ -163,11 +161,9 @@ class Connection { }); return recvBuf; - - StreamSubscription>? subscription; // Declaration moved here subscription = socket!.listen( - (List data) { + (List data) { recvBuf.addAll(data); if (recvBuf.length >= n) { subscription?.cancel(); @@ -178,9 +174,10 @@ class Connection { throw e; }, onDone: () { - print ("DEBUG ON DONE"); + print("DEBUG ON DONE"); if (recvBuf.length < n) { - throw SocketException('Connection closed before enough data was received'); + throw SocketException( + 'Connection closed before enough data was received'); } }, ); @@ -197,8 +194,8 @@ class Connection { return recvBuf; } - - Future> recv_message2(SocketWrapper socketwrapper, {Duration? timeout}) async { + Future> recv_message2(SocketWrapper socketwrapper, + {Duration? timeout}) async { if (timeout == null) { timeout = this.timeout; } @@ -237,31 +234,36 @@ class Connection { throw BadFrameError('Bad magic in frame: ${hex.encode(magic)}'); } - final byteData = ByteData.view(Uint8List.fromList(recvBuf.sublist(8, 12)).buffer); + final byteData = + ByteData.view(Uint8List.fromList(recvBuf.sublist(8, 12)).buffer); final messageLength = byteData.getUint32(0, Endian.big); if (messageLength > MAX_MSG_LENGTH) { - throw BadFrameError('Got a frame with msg_length=$messageLength > $MAX_MSG_LENGTH (max)'); + throw BadFrameError( + 'Got a frame with msg_length=$messageLength > $MAX_MSG_LENGTH (max)'); } - print("DEBUG recv_message2 3 - about to read the message body, messageLength: $messageLength"); + print( + "DEBUG recv_message2 3 - about to read the message body, messageLength: $messageLength"); - print ("DEBUG recvfbuf len is "); - print (recvBuf.length); - print ("bytes read is "); - print (bytesRead); - print ("message length is "); + print("DEBUG recvfbuf len is "); + print(recvBuf.length); + print("bytes read is "); + print(bytesRead); + print("message length is "); print(messageLength); if (recvBuf.length == bytesRead && bytesRead == 12 + messageLength) { final message = recvBuf.sublist(12, 12 + messageLength); - print("DEBUG recv_message2 4 - message received, length: ${message.length}"); + print( + "DEBUG recv_message2 4 - message received, length: ${message.length}"); print("DEBUG recv_message2 5 - message content: $message"); return message; } else { // Throwing exception if the length doesn't match - throw Exception('Message length mismatch: expected ${12 + messageLength} bytes, received ${recvBuf.length} bytes.'); + throw Exception( + 'Message length mismatch: expected ${12 + messageLength} bytes, received ${recvBuf.length} bytes.'); } } } @@ -273,13 +275,8 @@ class Connection { return []; } - Future> recv_message({Duration? timeout}) async { - - // DEPRECATED return []; } - - } // END OF CLASS diff --git a/lib/services/cashfusion/covert.dart b/lib/services/cashfusion/covert.dart index 825846548..d034f9e99 100644 --- a/lib/services/cashfusion/covert.dart +++ b/lib/services/cashfusion/covert.dart @@ -1,35 +1,35 @@ -import 'dart:math'; import 'dart:async'; -import 'dart:io'; import 'dart:collection'; -import 'connection.dart'; -import 'package:protobuf/protobuf.dart' as pb; -import 'comms.dart'; -import 'fusion.pb.dart'; import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:protobuf/protobuf.dart' as pb; + +import 'comms.dart'; +import 'connection.dart'; +import 'fusion.pb.dart'; const int TOR_COOLDOWN_TIME = 660; const int TIMEOUT_INACTIVE_CONNECTION = 120; - class FusionError implements Exception { String cause; FusionError(this.cause); } class Unrecoverable extends FusionError { - Unrecoverable(String cause): super(cause); + Unrecoverable(String cause) : super(cause); } - - Future isTorPort(String host, int port) async { if (port < 0 || port > 65535) { return false; } try { - Socket sock = await Socket.connect(host, port, timeout: Duration(milliseconds: 100)); + Socket sock = + await Socket.connect(host, port, timeout: Duration(milliseconds: 100)); sock.write("GET\n"); List data = await sock.first; sock.destroy(); @@ -43,13 +43,11 @@ Future isTorPort(String host, int port) async { return false; } - - class TorLimiter { Queue deque = Queue(); int lifetime; // Declare a lock here, may need a special Dart package for this - int _count=0; + int _count = 0; TorLimiter(this.lifetime); @@ -65,7 +63,6 @@ class TorLimiter { TorLimiter limiter = TorLimiter(TOR_COOLDOWN_TIME); - double randTrap(Random rng) { final sixth = 1.0 / 6; final f = rng.nextDouble(); @@ -80,8 +77,6 @@ double randTrap(Random rng) { } } - - class CovertConnection { Connection? connection; // replace dynamic with the type of your connection int? slotNum; @@ -124,7 +119,8 @@ class CovertSlot { int submitTimeout; pb.GeneratedMessage? subMsg; // The work to be done. bool done; // Whether last work requested is done. - CovertConnection? covConn; // which CovertConnection is assigned to work on this slot + CovertConnection? + covConn; // which CovertConnection is assigned to work on this slot CovertSlot(this.submitTimeout) : done = true; DateTime? t_submit; @@ -138,25 +134,21 @@ class CovertSlot { throw Unrecoverable('connection is null'); } - await sendPb(connection, CovertMessage, subMsg!, timeout: Duration(seconds: submitTimeout)); - var result = await recvPb(connection, CovertResponse, ['ok', 'error'], timeout: Duration(seconds: submitTimeout)); + await sendPb(connection, CovertMessage, subMsg!, + timeout: Duration(seconds: submitTimeout)); + var result = await recvPb(connection, CovertResponse, ['ok', 'error'], + timeout: Duration(seconds: submitTimeout)); if (result.item1 == 'error') { throw Unrecoverable('error from server: ${result.item2}'); } done = true; t_submit = DateTime.fromMillisecondsSinceEpoch(0); - covConn?.tPing = DateTime.fromMillisecondsSinceEpoch(0); // if a submission is done, no ping is needed. + covConn?.tPing = DateTime.fromMillisecondsSinceEpoch( + 0); // if a submission is done, no ping is needed. } - - - } - - - - class PrintError { // Declare properties here } @@ -165,7 +157,7 @@ class CovertSubmitter extends PrintError { // Declare properties here List slots; bool done = true; - String failure_exception= ""; + String failure_exception = ""; int num_slots; bool stopping = false; @@ -177,13 +169,13 @@ class CovertSubmitter extends PrintError { Object lock = Object(); int countFailed = 0; int countEstablished = 0; - int countAttempted=0; + int countAttempted = 0; Random rng = Random.secure(); int? randSpan; DateTime? stopTStart; - List spareConnections= []; + List spareConnections = []; String? failureException; - int submit_timeout=0; + int submit_timeout = 0; CovertSubmitter( String dest_addr, @@ -194,12 +186,11 @@ class CovertSubmitter extends PrintError { this.num_slots, double randSpan, // changed from int to double double submit_timeout) // changed from int to double - : slots = List.generate(num_slots, (index) => CovertSlot(submit_timeout.toInt())) { - + : slots = List.generate( + num_slots, (index) => CovertSlot(submit_timeout.toInt())) { // constructor body... } - void wakeAll() { for (var s in slots) { if (s.covConn != null) { @@ -218,7 +209,6 @@ class CovertSubmitter extends PrintError { } } - void stop([Exception? exception]) { if (this.stopping) { // already requested! @@ -226,13 +216,16 @@ class CovertSubmitter extends PrintError { } this.failureException = exception?.toString(); this.stopping = true; - var timeRemaining = this.stopTStart?.difference(DateTime.now()).inSeconds ?? 0; - print("Stopping; connections will close in approximately $timeRemaining seconds"); + var timeRemaining = + this.stopTStart?.difference(DateTime.now()).inSeconds ?? 0; + print( + "Stopping; connections will close in approximately $timeRemaining seconds"); this.wakeAll(); } // PYTHON USES MULTITHREADING, WHICH ISNT IMPLEMENTED HERE YET - void scheduleConnections(DateTime tStart, Duration tSpan, {int numSpares = 0, int connectTimeout = 10}) { + void scheduleConnections(DateTime tStart, Duration tSpan, + {int numSpares = 0, int connectTimeout = 10}) { var newConns = []; for (var sNum = 0; sNum < this.slots.length; sNum++) { @@ -244,8 +237,6 @@ class CovertSubmitter extends PrintError { if (myCovConn != null) { newConns.add(myCovConn); } - - } } @@ -258,16 +249,15 @@ class CovertSubmitter extends PrintError { for (var covConn in newConns) { covConn.connNumber = this.countAttempted; this.countAttempted++; - var connTime = tStart.add(Duration(seconds: (tSpan.inSeconds * randTrap(this.rng)).round())); - var randDelay = (this.randSpan ?? 0) * randTrap(this.rng); - - runConnection(covConn, connTime.millisecondsSinceEpoch, randDelay, connectTimeout); + var connTime = tStart.add( + Duration(seconds: (tSpan.inSeconds * randTrap(this.rng)).round())); + var randDelay = (this.randSpan ?? 0) * randTrap(this.rng); + runConnection( + covConn, connTime.millisecondsSinceEpoch, randDelay, connectTimeout); } } - - void scheduleSubmit(int slotNum, DateTime tStart, dynamic subMsg) { var slot = slots[slotNum]; @@ -275,14 +265,13 @@ class CovertSubmitter extends PrintError { slot.subMsg = subMsg; slot.done = false; - slot.t_submit= tStart; + slot.t_submit = tStart; var covConn = slot.covConn; if (covConn != null) { covConn.wakeup.complete(); } } - void scheduleSubmissions(DateTime tStart, List slotMessages) { // Convert to list (Dart does not have tuples) slotMessages = List.from(slotMessages); @@ -317,11 +306,11 @@ class CovertSubmitter extends PrintError { } } - - Future runConnection( - CovertConnection covConn, int connTime, double randDelay, int connectTimeout) async { + Future runConnection(CovertConnection covConn, int connTime, double randDelay, + int connectTimeout) async { // Main loop for connection thread - DateTime connDateTime = DateTime.fromMillisecondsSinceEpoch(connTime * 1000); + DateTime connDateTime = + DateTime.fromMillisecondsSinceEpoch(connTime * 1000); while (await covConn.waitWakeupOrTime(connDateTime)) { // if we are woken up before connection and stopping is happening, then just don't make a connection at all if (this.stopping) { @@ -350,11 +339,11 @@ class CovertSubmitter extends PrintError { try { final connection = await openConnection( this.destAddr!, this.destPort!, - connTimeout: connectTimeout.toDouble(), ssl: this.ssl, socksOpts: proxyOpts); + connTimeout: connectTimeout.toDouble(), + ssl: this.ssl, + socksOpts: proxyOpts); covConn.connection = connection; - } - - catch (e) { + } catch (e) { this.countFailed++; final tEnd = DateTime.now().millisecondsSinceEpoch; @@ -364,15 +353,13 @@ class CovertSubmitter extends PrintError { rethrow; } - - this.countEstablished++; + this.countEstablished++; final tEnd = DateTime.now().millisecondsSinceEpoch; print( '[${covConn.connNumber}] connection established after ${((tEnd - tBegin) / 1000).toStringAsFixed(3)}s'); - covConn.delay = (randTrap(this.rng) ?? 0) * (this.randSpan ?? 0); var lastActionTime = DateTime.now().millisecondsSinceEpoch; @@ -381,7 +368,7 @@ class CovertSubmitter extends PrintError { while (!this.stopping) { DateTime? nextTime; final slotNum = covConn.slotNum; - Function()? action; // callback to hold the action function + Function()? action; // callback to hold the action function // Second preference: submit something if (slotNum != null) { @@ -396,7 +383,8 @@ class CovertSubmitter extends PrintError { } // Last preference: wait doing nothing if (nextTime == null) { - nextTime = DateTime.now().add(Duration(seconds: TIMEOUT_INACTIVE_CONNECTION)); + nextTime = DateTime.now() + .add(Duration(seconds: TIMEOUT_INACTIVE_CONNECTION)); action = covConn.inactive; } @@ -423,7 +411,9 @@ class CovertSubmitter extends PrintError { // STATE 3 - stopping while (true) { - final stopTime = this.stopTStart?.add(Duration(seconds: randDelay.toInt())) ?? DateTime.now(); + final stopTime = + this.stopTStart?.add(Duration(seconds: randDelay.toInt())) ?? + DateTime.now(); if (!(await covConn.waitWakeupOrTime(stopTime))) { break; @@ -435,34 +425,33 @@ class CovertSubmitter extends PrintError { // in case of any problem, record the exception and if we have a slot, reassign it. final exception = e; - final slotNum = covConn.slotNum; - if (slotNum != null) { - try { - final spare = this.spareConnections.removeLast(); - // Found a spare. - this.slots[slotNum].covConn = spare; - spare.slotNum = slotNum; - spare.wakeup.complete(); // python code is using set, possibly dealing wiht multi thread...double check this is ok. + final slotNum = covConn.slotNum; + if (slotNum != null) { + try { + final spare = this.spareConnections.removeLast(); + // Found a spare. + this.slots[slotNum].covConn = spare; + spare.slotNum = slotNum; + spare.wakeup + .complete(); // python code is using set, possibly dealing wiht multi thread...double check this is ok. - covConn.slotNum = null; - } catch (e) { - // We failed, and there are no spares. Party is over! - - if (exception is Exception) { - this.stop(exception); - } else { - // Handle the case where the exception is not an instance of Exception - } + covConn.slotNum = null; + } catch (e) { + // We failed, and there are no spares. Party is over! + if (exception is Exception) { + this.stop(exception); + } else { + // Handle the case where the exception is not an instance of Exception } } + } } finally { covConn.connection?.close(); } } } - void checkOk() { // Implement checkOk logic here var e = this.failure_exception; @@ -474,26 +463,21 @@ class CovertSubmitter extends PrintError { void checkConnected() { // Implement checkConnected logic here this.checkOk(); - var numMissing = this.slots - .where((s) => s.covConn?.connection == null) - .length; + var numMissing = + this.slots.where((s) => s.covConn?.connection == null).length; if (numMissing > 0) { throw FusionError( - "Covert connections were too slow ($numMissing incomplete out of ${this - .slots.length})."); + "Covert connections were too slow ($numMissing incomplete out of ${this.slots.length})."); } } void checkDone() { // Implement checkDone logic here this.checkOk(); - var numMissing = this.slots - .where((s) => !s.done) - .length; + var numMissing = this.slots.where((s) => !s.done).length; if (numMissing > 0) { throw FusionError( - "Covert submissions were too slow ($numMissing incomplete out of ${this - .slots.length})."); + "Covert submissions were too slow ($numMissing incomplete out of ${this.slots.length})."); } } -} \ No newline at end of file +} diff --git a/lib/services/cashfusion/encrypt.dart b/lib/services/cashfusion/encrypt.dart index 39ab8a2c5..2a6e00a6b 100644 --- a/lib/services/cashfusion/encrypt.dart +++ b/lib/services/cashfusion/encrypt.dart @@ -1,17 +1,20 @@ -import 'dart:convert'; import 'dart:typed_data'; -import 'package:pointycastle/pointycastle.dart' hide Mac; + import 'package:crypto/crypto.dart' as crypto; import 'package:cryptography/cryptography.dart'; +import 'package:pointycastle/pointycastle.dart' hide Mac; + import 'util.dart'; final ECDomainParameters params = ECDomainParameters('secp256k1'); final BigInt order = params.n; class EncryptionFailed implements Exception {} + class DecryptionFailed implements Exception {} -Future encrypt(Uint8List message, ECPoint pubkey, {int? padToLength}) async { +Future encrypt(Uint8List message, ECPoint pubkey, + {int? padToLength}) async { ECPoint pubpoint; try { pubpoint = Util.ser_to_point(pubkey.getEncoded(true), params); @@ -27,22 +30,27 @@ Future encrypt(Uint8List message, ECPoint pubkey, {int? padToLength}) var pubpoint_times_nonceSec = pubpoint * nonceSec; if (pubpoint_times_nonceSec == null) { - throw Exception('Multiplication of pubpoint with nonceSec resulted in null'); + throw Exception( + 'Multiplication of pubpoint with nonceSec resulted in null'); } - var key = crypto.sha256.convert(Util.point_to_ser(pubpoint_times_nonceSec, true)).bytes; + var key = crypto.sha256 + .convert(Util.point_to_ser(pubpoint_times_nonceSec, true)) + .bytes; - - - var plaintext = Uint8List(4 + message.length)..buffer.asByteData().setUint32(0, message.length, Endian.big)..setRange(4, 4 + message.length, message); + var plaintext = Uint8List(4 + message.length) + ..buffer.asByteData().setUint32(0, message.length, Endian.big) + ..setRange(4, 4 + message.length, message); if (padToLength == null) { - padToLength = ((plaintext.length + 15) ~/ 16) * 16; // round up to nearest 16 + padToLength = + ((plaintext.length + 15) ~/ 16) * 16; // round up to nearest 16 } else if (padToLength % 16 != 0) { throw ArgumentError('$padToLength not multiple of 16'); } if (padToLength < plaintext.length) { throw ArgumentError('$padToLength < ${plaintext.length}'); } - plaintext = Uint8List(padToLength)..setRange(0, message.length + 4, plaintext); + plaintext = Uint8List(padToLength) + ..setRange(0, message.length + 4, plaintext); final secretKey = SecretKey(key); @@ -50,16 +58,20 @@ Future encrypt(Uint8List message, ECPoint pubkey, {int? padToLength}) final cipher = AesCbc.with128bits(macAlgorithm: macAlgorithm); - final nonce = Uint8List(16); // Random nonce - final secretBox = await cipher.encrypt(plaintext, secretKey: secretKey, nonce: nonce); + final secretBox = + await cipher.encrypt(plaintext, secretKey: secretKey, nonce: nonce); final ciphertext = secretBox.cipherText; - return Uint8List(noncePub.length + ciphertext.length + secretBox.mac.bytes.length) + return Uint8List( + noncePub.length + ciphertext.length + secretBox.mac.bytes.length) ..setRange(0, noncePub.length, noncePub) ..setRange(noncePub.length, noncePub.length + ciphertext.length, ciphertext) - ..setRange(noncePub.length + ciphertext.length, noncePub.length + ciphertext.length + secretBox.mac.bytes.length, secretBox.mac.bytes); + ..setRange( + noncePub.length + ciphertext.length, + noncePub.length + ciphertext.length + secretBox.mac.bytes.length, + secretBox.mac.bytes); } Future decryptWithSymmkey(Uint8List data, Uint8List key) async { @@ -75,7 +87,8 @@ Future decryptWithSymmkey(Uint8List data, Uint8List key) async { final cipher = AesCbc.with128bits(macAlgorithm: Hmac.sha256()); final nonce = Uint8List(16); // Random nonce - final secretBox = SecretBox(ciphertext, mac: Mac(data.sublist(data.length - 16)), nonce: nonce); + final secretBox = SecretBox(ciphertext, + mac: Mac(data.sublist(data.length - 16)), nonce: nonce); final plaintext = await cipher.decrypt(secretBox, secretKey: secretKey); if (plaintext.length < 4) { @@ -86,15 +99,14 @@ Future decryptWithSymmkey(Uint8List data, Uint8List key) async { ByteData byteData = ByteData.sublistView(uint8list); var msgLength = byteData.getUint32(0, Endian.big); - if (msgLength + 4 > plaintext.length) { throw DecryptionFailed(); } return Uint8List.fromList(plaintext.sublist(4, 4 + msgLength)); - } -Future> decrypt(Uint8List data, ECPrivateKey privkey) async { +Future> decrypt( + Uint8List data, ECPrivateKey privkey) async { if (data.length < 33 + 16 + 16) { throw DecryptionFailed(); } @@ -122,4 +134,3 @@ Future> decrypt(Uint8List data, ECPrivateKey privkey var decryptedData = await decryptWithSymmkey(data, Uint8List.fromList(key)); return Tuple(decryptedData, Uint8List.fromList(key)); } - diff --git a/lib/services/cashfusion/fusion.dart b/lib/services/cashfusion/fusion.dart index bcb03ac5a..573e38d5c 100644 --- a/lib/services/cashfusion/fusion.dart +++ b/lib/services/cashfusion/fusion.dart @@ -1,27 +1,25 @@ -import 'socketwrapper.dart'; -import 'package:protobuf/protobuf.dart'; +import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'fusion.pb.dart'; -import 'util.dart'; import 'dart:typed_data'; -import 'package:fixnum/fixnum.dart'; -import 'pedersen.dart'; -import 'dart:io'; -import 'package:crypto/crypto.dart'; -import 'dart:async'; -import 'comms.dart'; -import 'protocol.dart'; -import 'package:fixnum/fixnum.dart'; // so int and intt64 can be combined in some protobuff code -import 'encrypt.dart'; -import "package:pointycastle/export.dart"; -import 'covert.dart'; -import 'connection.dart'; + import 'package:collection/collection.dart'; -import 'package:convert/convert.dart'; -import 'validation.dart'; +import 'package:crypto/crypto.dart'; +import 'package:fixnum/fixnum.dart'; +import "package:pointycastle/export.dart"; +import 'package:protobuf/protobuf.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'comms.dart'; +import 'connection.dart'; +import 'covert.dart'; +import 'encrypt.dart'; +import 'fusion.pb.dart'; +import 'pedersen.dart'; +import 'protocol.dart'; +import 'socketwrapper.dart'; +import 'util.dart'; +import 'validation.dart'; class FusionError implements Exception { final String message; @@ -36,17 +34,11 @@ class ComponentResult { final Proof proof; final Uint8List privateKey; final dynamic pedersenAmount; // replace dynamic with actual type - final dynamic pedersenNonce; // replace dynamic with actual type + final dynamic pedersenNonce; // replace dynamic with actual type - ComponentResult( - this.commitment, - this.counter, - this.component, - this.proof, + ComponentResult(this.commitment, this.counter, this.component, this.proof, this.privateKey, - {this.pedersenAmount, - this.pedersenNonce} - ); + {this.pedersenAmount, this.pedersenNonce}); } class Transaction { @@ -55,8 +47,8 @@ class Transaction { Transaction(); - - static Tuple txFromComponents(List allComponents, List sessionHash) { + static Tuple txFromComponents( + List allComponents, List sessionHash) { Transaction tx = Transaction(); // Initialize a new Transaction // This should be based on wallet layer... implement the logic of constructing the transaction from components // For now, it just initializes Inputs and Outputs as empty lists @@ -91,18 +83,22 @@ class Transaction { } } - class Input { List prevTxid; int prevIndex; List pubKey; int amount; - List signatures =[]; + List signatures = []; - Input({required this.prevTxid, required this.prevIndex, required this.pubKey, required this.amount}); + Input( + {required this.prevTxid, + required this.prevIndex, + required this.pubKey, + required this.amount}); int sizeOfInput() { - assert(1 < pubKey.length && pubKey.length < 76); // need to assume regular push opcode + assert(1 < pubKey.length && + pubKey.length < 76); // need to assume regular push opcode return 108 + pubKey.length; } @@ -120,10 +116,9 @@ class Input { return ""; } - static Input fromInputComponent(InputComponent inputComponent) { return Input( - prevTxid: inputComponent.prevTxid, // Make sure the types are matching + prevTxid: inputComponent.prevTxid, // Make sure the types are matching prevIndex: inputComponent.prevIndex.toInt(), pubKey: inputComponent.pubkey, amount: inputComponent.amount.toInt(), @@ -138,21 +133,19 @@ class Input { amount: utxo.value, ); } - } - - class Output { int value; Address addr; - int amount=0; + int amount = 0; Output({required this.value, required this.addr}); int sizeOfOutput() { - List scriptpubkey = addr.toScript(); // assuming addr.toScript() returns List that represents the scriptpubkey + List scriptpubkey = addr + .toScript(); // assuming addr.toScript() returns List that represents the scriptpubkey assert(scriptpubkey.length < 253); return 9 + scriptpubkey.length; } @@ -164,23 +157,20 @@ class Output { addr: address, ); } - } - - // Class to handle fusion class Fusion { - - List coins = []; //"coins" and "inputs" are often synonmous in the original python code. - List outputs =[]; + List coins = + []; //"coins" and "inputs" are often synonmous in the original python code. + List outputs = []; bool server_connected_and_greeted = false; bool stopping = false; bool stopping_if_not_running = false; - String stopReason=""; - String tor_host=""; - bool server_ssl= false; - String server_host ="cashfusion.stackwallet.com"; + String stopReason = ""; + String tor_host = ""; + bool server_ssl = false; + String server_host = "cashfusion.stackwallet.com"; int server_port = 8787; //String server_host = "fusion.servo.cash"; @@ -188,21 +178,22 @@ class Fusion { int tor_port = 0; int roundcount = 0; - String txid=""; + String txid = ""; - Tuple status = Tuple("", ""); + Tuple status = Tuple("", ""); Connection? connection; - int numComponents =0; - double componentFeeRate=0; - double minExcessFee=0; - double maxExcessFee=0; - List availableTiers =[]; + int numComponents = 0; + double componentFeeRate = 0; + double minExcessFee = 0; + double maxExcessFee = 0; + List availableTiers = []; - int maxOutputs=0; - int safety_sum_in =0; + int maxOutputs = 0; + int safety_sum_in = 0; Map safety_exess_fees = {}; - Map> tierOutputs ={}; // not sure if this should be using outputs class. + Map> tierOutputs = + {}; // not sure if this should be using outputs class. int inactiveTimeLimit = 0; int tier = 0; @@ -216,12 +207,12 @@ class Fusion { Uint8List covertDomainB = Uint8List(0); var txInputIndices; - Transaction tx= Transaction(); - List myComponentIndexes=[]; - List myCommitmentIndexes=[]; - Set badComponents ={}; + Transaction tx = Transaction(); + List myComponentIndexes = []; + List myCommitmentIndexes = []; + Set badComponents = {}; - Fusion () { + Fusion() { //initializeConnection(host, port) } /* @@ -239,59 +230,53 @@ class Fusion { } Future fusion_run() async { - - print ("DEBUG FUSION 223...fusion run...."); + print("DEBUG FUSION 223...fusion run...."); try { - try { - // Check compatibility - This was done in python version to see if fast libsec installed. // For now , in dart, just pass this test. ; - } on Exception catch(e) { + } on Exception catch (e) { // handle exception, rethrow as a custom FusionError throw FusionError("Incompatible: " + e.toString()); } // Check if can connect to Tor proxy, if not, raise FusionError. Empty String treated as no host. - if (tor_host.isNotEmpty && tor_port != 0 && !await isTorPort(tor_host, tor_port)) { + if (tor_host.isNotEmpty && + tor_port != 0 && + !await isTorPort(tor_host, tor_port)) { throw FusionError("Can't connect to Tor proxy at $tor_host:$tor_port"); } try { // Check stop condition check_stop(running: false); - } - catch (e) { - print (e); + } catch (e) { + print(e); } try { // Check coins check_coins(); + } catch (e) { + print(e); } - catch (e) { - print (e); - } - - // Connect to server status = Tuple("connecting", ""); try { - connection = await openConnection(server_host, server_port, connTimeout: 5.0, defaultTimeout: 5.0, ssl: server_ssl); - } catch (e) { + connection = await openConnection(server_host, server_port, + connTimeout: 5.0, defaultTimeout: 5.0, ssl: server_ssl); + } catch (e) { print("Connect failed: $e"); String sslstr = server_ssl ? ' SSL ' : ''; - throw FusionError('Could not connect to $sslstr$server_host:$server_port'); + throw FusionError( + 'Could not connect to $sslstr$server_host:$server_port'); } - // Once connection is successful, wrap operations inside this block // Within this block, version checks, downloads server params, handles coins and runs rounds try { - - SocketWrapper socketwrapper = SocketWrapper(server_host, server_port); await socketwrapper.connect(); @@ -309,8 +294,7 @@ class Fusion { throw FusionError('Started with no coins'); return; } - } - catch (e) { + } catch (e) { print(e); return; } @@ -321,12 +305,10 @@ class Fusion { // Register for tiers, wait for a pool. await registerAndWait(socketwrapper); - - print ("FUSION DEBUG 273"); - print ("RETURNING early in fusion_run...."); + print("FUSION DEBUG 273"); + print("RETURNING early in fusion_run...."); return; - // launch the covert submitter CovertSubmitter covert = await start_covert(); try { @@ -361,15 +343,15 @@ class Fusion { // Wait for transaction to show up in wallets // Set status to 'complete' with txid - - } on FusionError catch(err) { + } on FusionError catch (err) { print('Failed: ${err}'); status.item1 = "failed"; - status.item2 = err.toString(); // setting the error message - } catch(exc) { + status.item2 = err.toString(); // setting the error message + } catch (exc) { print('Exception: ${exc}'); status.item1 = "failed"; - status.item2= "Exception: ${exc.toString()}"; // setting the exception message + status.item2 = + "Exception: ${exc.toString()}"; // setting the exception message } finally { clear_coins(); if (status.item1 != 'complete') { @@ -381,22 +363,15 @@ class Fusion { } } } - - - } // end fusion_run function. - - - - + } // end fusion_run function. Future start_covert() async { // Function implementation here... // For now, just return a new instance of CovertSubmitter - return CovertSubmitter("dummy",0,true,"some_host",0,0,0,0); + return CovertSubmitter("dummy", 0, true, "some_host", 0, 0, 0, 0); } - Future run_round(CovertSubmitter covert) async { // function implementation here... @@ -408,7 +383,6 @@ class Fusion { // Function implementation goes here } - void stop([String reason = 'stopped', bool notIfRunning = false]) { if (stopping) { return; @@ -433,18 +407,18 @@ class Fusion { } } -void check_coins() { + void check_coins() { // Implement by calling wallet layer to check the coins are ok. return; -} + } static void foo() { -print ("hello"); -} + print("hello"); + } - void clear_coins() { + void clear_coins() { coins = []; - } + } void addCoins(List newCoins) { coins.addAll(newCoins); @@ -454,35 +428,36 @@ print ("hello"); return; } -static bool walletCanFuse() { - return true; + static bool walletCanFuse() { + return true; - // Implement logic here to return false if the wallet can't fuse. (If its read only or non P2PKH) -} - -static double nextDoubleNonZero(Random rng) { - double value = 0.0; - while (value == 0.0) { - value = rng.nextDouble(); + // Implement logic here to return false if the wallet can't fuse. (If its read only or non P2PKH) } - return value; -} - static List? randomOutputsForTier(Random rng, int inputAmount, int scale, int offset, int maxCount) { + static double nextDoubleNonZero(Random rng) { + double value = 0.0; + while (value == 0.0) { + value = rng.nextDouble(); + } + return value; + } + static List? randomOutputsForTier( + Random rng, int inputAmount, int scale, int offset, int maxCount) { if (inputAmount < offset) { return []; } double lambd = 1.0 / scale; int remaining = inputAmount; - List values = []; // list of fractional random values without offset - bool didBreak = false; // Add this flag to detect when a break is encountered + List values = []; // list of fractional random values without offset + bool didBreak = + false; // Add this flag to detect when a break is encountered for (int i = 0; i < maxCount + 1; i++) { double val = -lambd * log(nextDoubleNonZero(rng)); remaining -= (val.ceil() + offset); if (remaining < 0) { - didBreak = true; // If you break, set this flag to true + didBreak = true; // If you break, set this flag to true break; } values.add(val); @@ -516,120 +491,115 @@ static double nextDoubleNonZero(Random rng) { double rescale = desiredRandomSum / cumsum[cumsum.length - 1]; List normedCumsum = cumsum.map((v) => (rescale * v).round()).toList(); - assert(normedCumsum[normedCumsum.length - 1] == desiredRandomSum, 'Last element of normedCumsum is not equal to desiredRandomSum'); + assert(normedCumsum[normedCumsum.length - 1] == desiredRandomSum, + 'Last element of normedCumsum is not equal to desiredRandomSum'); List differences = []; - differences.add(normedCumsum[0]); // First element + differences.add(normedCumsum[0]); // First element for (int i = 1; i < normedCumsum.length; i++) { differences.add(normedCumsum[i] - normedCumsum[i - 1]); } List result = differences.map((d) => offset + d).toList(); - assert(result.reduce((a, b) => a + b) == inputAmount, 'Sum of result is not equal to inputAmount'); + assert(result.reduce((a, b) => a + b) == inputAmount, + 'Sum of result is not equal to inputAmount'); return result; - } + static List genComponents( + int numBlanks, List inputs, List outputs, int feerate) { + assert(numBlanks >= 0); - static List genComponents(int numBlanks, List inputs, List outputs, int feerate) { - assert(numBlanks >= 0); + List> components = []; - List> components = []; + // Set up Pedersen setup instance + Uint8List HBytes = Uint8List.fromList( + [0x02] + 'CashFusion gives us fungibility.'.codeUnits); + ECDomainParameters params = ECDomainParameters('secp256k1'); + ECPoint? HMaybe = params.curve.decodePoint(HBytes); + if (HMaybe == null) { + throw Exception('Failed to decode point'); + } + ECPoint H = HMaybe; + PedersenSetup setup = PedersenSetup(H); - // Set up Pedersen setup instance - Uint8List HBytes = Uint8List.fromList([0x02] + 'CashFusion gives us fungibility.'.codeUnits); - ECDomainParameters params = ECDomainParameters('secp256k1'); - ECPoint? HMaybe = params.curve.decodePoint(HBytes); - if (HMaybe == null) { - throw Exception('Failed to decode point'); - } - ECPoint H = HMaybe; - PedersenSetup setup = PedersenSetup(H); + for (Input input in inputs) { + int fee = Util.componentFee(input.sizeOfInput(), feerate); - for (Input input in inputs) { - int fee = Util.componentFee(input.sizeOfInput(), feerate); + var comp = Component(); + comp.input = InputComponent( + prevTxid: Uint8List.fromList(input.prevTxid.reversed.toList()), + prevIndex: input.prevIndex, + pubkey: input.pubKey, + amount: Int64(input.amount)); + components.add(Tuple(comp, input.amount - fee)); + } - var comp = Component(); - comp.input = InputComponent( - prevTxid: Uint8List.fromList(input.prevTxid.reversed.toList()), - prevIndex: input.prevIndex, - pubkey: input.pubKey, - amount: Int64(input.amount) - ); - components.add(Tuple(comp, input.amount - fee)); - } + for (Output output in outputs) { + var script = output.addr.toScript(); + int fee = Util.componentFee(output.sizeOfOutput(), feerate); - for (Output output in outputs) { - var script = output.addr.toScript(); - int fee = Util.componentFee(output.sizeOfOutput(), feerate); + var comp = Component(); + comp.output = + OutputComponent(scriptpubkey: script, amount: Int64(output.value)); + components.add(Tuple(comp, -output.value - fee)); + } - var comp = Component(); - comp.output = OutputComponent( - scriptpubkey: script, - amount: Int64(output.value) - ); - components.add(Tuple(comp, -output.value - fee)); - } + for (int i = 0; i < numBlanks; i++) { + var comp = Component(); + comp.blank = BlankComponent(); + components.add(Tuple(comp, 0)); + } - for (int i = 0; i < numBlanks; i++) { - var comp = Component(); - comp.blank = BlankComponent(); - components.add(Tuple(comp, 0)); - } + List resultList = []; - List resultList = []; + components.asMap().forEach((cnum, Tuple componentTuple) { + Uint8List salt = Util.tokenBytes(32); + componentTuple.item1.saltCommitment = Util.sha256(salt); + var compser = componentTuple.item1.writeToBuffer(); - components.asMap().forEach((cnum, Tuple componentTuple) { - Uint8List salt = Util.tokenBytes(32); - componentTuple.item1.saltCommitment = Util.sha256(salt); - var compser = componentTuple.item1.writeToBuffer(); - - Tuple keyPair = Util.genKeypair(); - Uint8List privateKey = keyPair.item1; - Uint8List pubKey = keyPair.item2; - - Commitment commitmentInstance = setup.commit(BigInt.from(componentTuple.item2)); - Uint8List amountCommitment = commitmentInstance.PUncompressed; + Tuple keyPair = Util.genKeypair(); + Uint8List privateKey = keyPair.item1; + Uint8List pubKey = keyPair.item2; + Commitment commitmentInstance = + setup.commit(BigInt.from(componentTuple.item2)); + Uint8List amountCommitment = commitmentInstance.PUncompressed; // Convert BigInt nonce to Uint8List - Uint8List pedersenNonce = Uint8List.fromList([int.parse(commitmentInstance.nonce.toRadixString(16), radix: 16)]); + Uint8List pedersenNonce = Uint8List.fromList( + [int.parse(commitmentInstance.nonce.toRadixString(16), radix: 16)]); // Generating initial commitment - InitialCommitment commitment = InitialCommitment( - saltedComponentHash: Util.sha256(Uint8List.fromList([...compser, ...salt])), - amountCommitment: amountCommitment, - communicationKey: pubKey - ); + InitialCommitment commitment = InitialCommitment( + saltedComponentHash: + Util.sha256(Uint8List.fromList([...compser, ...salt])), + amountCommitment: amountCommitment, + communicationKey: pubKey); - Uint8List commitser = commitment.writeToBuffer(); + Uint8List commitser = commitment.writeToBuffer(); - // Generating proof - Proof proof = Proof( - componentIdx: cnum, - salt: salt, - pedersenNonce: pedersenNonce - ); + // Generating proof + Proof proof = + Proof(componentIdx: cnum, salt: salt, pedersenNonce: pedersenNonce); - // Adding result to list - resultList.add(ComponentResult(commitser, cnum, compser, proof, privateKey)); - }); + // Adding result to list + resultList + .add(ComponentResult(commitser, cnum, compser, proof, privateKey)); + }); - return resultList; -} + return resultList; + } - - Future recv2(SocketWrapper socketwrapper, List expectedMsgNames, {Duration? timeout}) async { + Future recv2( + SocketWrapper socketwrapper, List expectedMsgNames, + {Duration? timeout}) async { if (connection == null) { throw FusionError('Connection not initialized'); } var result = await recvPb2( - socketwrapper, - connection!, - ServerMessage, - expectedMsgNames, - timeout: timeout - ); + socketwrapper, connection!, ServerMessage, expectedMsgNames, + timeout: timeout); var submsg = result.item1; var mtype = result.item2; @@ -641,20 +611,15 @@ static double nextDoubleNonZero(Random rng) { return submsg; } - - - Future recv(List expectedMsgNames, {Duration? timeout}) async { + Future recv(List expectedMsgNames, + {Duration? timeout}) async { // DEPRECATED if (connection == null) { throw FusionError('Connection not initialized'); } - var result = await recvPb( - connection!, - ServerMessage, - expectedMsgNames, - timeout: timeout - ); + var result = await recvPb(connection!, ServerMessage, expectedMsgNames, + timeout: timeout); var submsg = result.item1; var mtype = result.item2; @@ -666,7 +631,6 @@ static double nextDoubleNonZero(Random rng) { return submsg; } - Future send(GeneratedMessage submsg, {Duration? timeout}) async { // DEPRECATED if (connection != null) { @@ -674,18 +638,16 @@ static double nextDoubleNonZero(Random rng) { } else { print('Connection is null'); } - } - - Future send2(SocketWrapper socketwrapper, GeneratedMessage submsg, {Duration? timeout}) async { - + Future send2(SocketWrapper socketwrapper, GeneratedMessage submsg, + {Duration? timeout}) async { if (connection != null) { - await sendPb2(socketwrapper, connection!, ClientMessage, submsg, timeout: timeout); + await sendPb2(socketwrapper, connection!, ClientMessage, submsg, + timeout: timeout); } else { print('Connection is null'); } - } Future greet(SocketWrapper socketwrapper) async { @@ -693,8 +655,7 @@ static double nextDoubleNonZero(Random rng) { version: Uint8List.fromList(utf8.encode(Protocol.VERSION)), genesisHash: Util.get_current_genesis_hash()); - ClientMessage clientMessage = ClientMessage() - ..clienthello = clientHello; + ClientMessage clientMessage = ClientMessage()..clienthello = clientHello; //deprecated //Connection greet_connection_1 = Connection.withoutSocket(); @@ -704,42 +665,42 @@ static double nextDoubleNonZero(Random rng) { SocketWrapper socketwrapper = SocketWrapper(server_host, server_port); await socketwrapper.connect(); */ - send2(socketwrapper,clientMessage); - + send2(socketwrapper, clientMessage); var replyMsg = await recv2(socketwrapper, ['serverhello']); if (replyMsg is ServerMessage) { - ServerHello reply = replyMsg.serverhello; + ServerHello reply = replyMsg.serverhello; - numComponents = reply.numComponents; - componentFeeRate = reply.componentFeerate.toDouble(); - minExcessFee = reply.minExcessFee.toDouble(); - maxExcessFee = reply.maxExcessFee.toDouble(); - availableTiers = reply.tiers.map((tier) => tier.toInt()).toList(); + numComponents = reply.numComponents; + componentFeeRate = reply.componentFeerate.toDouble(); + minExcessFee = reply.minExcessFee.toDouble(); + maxExcessFee = reply.maxExcessFee.toDouble(); + availableTiers = reply.tiers.map((tier) => tier.toInt()).toList(); - // Enforce some sensible limits, in case server is crazy - if (componentFeeRate > Protocol.MAX_COMPONENT_FEERATE) { - throw FusionError('excessive component feerate from server'); - } - if (minExcessFee > 400) { // note this threshold should be far below MAX_EXCESS_FEE - throw FusionError('excessive min excess fee from server'); - } - if (minExcessFee > maxExcessFee) { - throw FusionError('bad config on server: fees'); - } - if (numComponents < Protocol.MIN_TX_COMPONENTS * 1.5) { - throw FusionError('bad config on server: num_components'); - } + // Enforce some sensible limits, in case server is crazy + if (componentFeeRate > Protocol.MAX_COMPONENT_FEERATE) { + throw FusionError('excessive component feerate from server'); + } + if (minExcessFee > 400) { + // note this threshold should be far below MAX_EXCESS_FEE + throw FusionError('excessive min excess fee from server'); + } + if (minExcessFee > maxExcessFee) { + throw FusionError('bad config on server: fees'); + } + if (numComponents < Protocol.MIN_TX_COMPONENTS * 1.5) { + throw FusionError('bad config on server: num_components'); + } } else { - throw Exception('Received unexpected message type: ${replyMsg.runtimeType}'); + throw Exception( + 'Received unexpected message type: ${replyMsg.runtimeType}'); } } - Future allocateOutputs(socketwrapper) async { - print ("DBUG allocateoutputs 746"); + print("DBUG allocateoutputs 746"); - print ("CHECK socketwrapper 746"); + print("CHECK socketwrapper 746"); socketwrapper.status(); assert(['setup', 'connecting'].contains(status.item1)); @@ -760,12 +721,15 @@ static double nextDoubleNonZero(Random rng) { int numDistinct = inputs.map((e) => e.value).toSet().length; int minOutputs = max(Protocol.MIN_TX_COMPONENTS - numDistinct, 1); if (maxOutputs < minOutputs) { - throw FusionError('Too few distinct inputs selected ($numDistinct); cannot satisfy output count constraint (>= $minOutputs, <= $maxOutputs)'); + throw FusionError( + 'Too few distinct inputs selected ($numDistinct); cannot satisfy output count constraint (>= $minOutputs, <= $maxOutputs)'); } - int sumInputsValue = inputs.map((e) => e.value).reduce((a, b) => a + b); - int inputFees = inputs.map((e) => Util.componentFee(e.sizeOfInput(), componentFeeRate.toInt())).reduce((a, b) => a + b); + int inputFees = inputs + .map( + (e) => Util.componentFee(e.sizeOfInput(), componentFeeRate.toInt())) + .reduce((a, b) => a + b); int availForOutputs = sumInputsValue - inputFees - minExcessFee.toInt(); int feePerOutput = Util.componentFee(34, componentFeeRate.toInt()); @@ -779,12 +743,15 @@ static double nextDoubleNonZero(Random rng) { var rng = Random(); var seed = List.generate(32, (_) => rng.nextInt(256)); - print ("DBUG allocateoutputs 785"); + print("DBUG allocateoutputs 785"); tierOutputs = {}; var excessFees = {}; for (var scale in availableTiers) { int fuzzFeeMax = scale ~/ 1000000; - int fuzzFeeMaxReduced = min(fuzzFeeMax, min(Protocol.MAX_EXCESS_FEE - minExcessFee.toInt(), maxExcessFee.toInt())); + int fuzzFeeMaxReduced = min( + fuzzFeeMax, + min(Protocol.MAX_EXCESS_FEE - minExcessFee.toInt(), + maxExcessFee.toInt())); assert(fuzzFeeMaxReduced >= 0); int fuzzFee = rng.nextInt(fuzzFeeMaxReduced + 1); @@ -794,13 +761,12 @@ static double nextDoubleNonZero(Random rng) { continue; } - var outputs = randomOutputsForTier(rng, reducedAvailForOutputs, scale, offsetPerOutput, maxOutputs); + var outputs = randomOutputsForTier( + rng, reducedAvailForOutputs, scale, offsetPerOutput, maxOutputs); if (outputs != null) { - print (outputs); + print(outputs); } if (outputs == null || outputs.length < minOutputs) { - - continue; } outputs = outputs.map((o) => o - feePerOutput).toList(); @@ -811,20 +777,16 @@ static double nextDoubleNonZero(Random rng) { tierOutputs[scale] = outputs!; } - print('Possible tiers: $tierOutputs'); - print ("CHECK socketwrapper 839"); + print("CHECK socketwrapper 839"); socketwrapper.status(); safety_sum_in = sumInputsValue; safety_exess_fees = excessFees; return; } - - Future registerAndWait(SocketWrapper socketwrapper) async { - // msg can be different classes depending on which protobuf msg is sent. dynamic? msg; @@ -832,44 +794,44 @@ static double nextDoubleNonZero(Random rng) { var tiersSorted = tierOutputs.keys.toList()..sort(); if (tierOutputs.isEmpty) { - throw FusionError('No outputs available at any tier (selected inputs were too small / too large).'); + throw FusionError( + 'No outputs available at any tier (selected inputs were too small / too large).'); } print('registering for tiers: $tiersSorted'); - int self_fuse = 1; // Temporary value for now - var cashfusionTag = [1];// temp value for now - - + int self_fuse = 1; // Temporary value for now + var cashfusionTag = [1]; // temp value for now check_stop(running: false); check_coins(); - var tags = [JoinPools_PoolTag(id: cashfusionTag, limit: self_fuse)]; // Create JoinPools message - JoinPools joinPools = JoinPools( - tiers: tiersSorted.map((i) => Int64(i)).toList(), - tags: tags - ); + JoinPools joinPools = + JoinPools(tiers: tiersSorted.map((i) => Int64(i)).toList(), tags: tags); // Wrap it in a ClientMessage - ClientMessage clientMessage = ClientMessage() - ..joinpools = joinPools; + ClientMessage clientMessage = ClientMessage()..joinpools = joinPools; send2(socketwrapper, clientMessage); - status = Tuple('waiting', 'Registered for tiers'); - var tiersStrings = {for (var entry in tierOutputs.entries) entry.key: (entry.key * 1e-8).toStringAsFixed(8).replaceAll(RegExp(r'0+$'), '')}; + var tiersStrings = { + for (var entry in tierOutputs.entries) + entry.key: + (entry.key * 1e-8).toStringAsFixed(8).replaceAll(RegExp(r'0+$'), '') + }; while (true) { - var msg = await recv2(socketwrapper,['tierstatusupdate', 'fusionbegin'], timeout: Duration(seconds: 10)); + var msg = await recv2(socketwrapper, ['tierstatusupdate', 'fusionbegin'], + timeout: Duration(seconds: 10)); var fieldInfoFusionBegin = msg.info_.byName["fusionbegin"]; - if (fieldInfoFusionBegin != null && msg.hasField(fieldInfoFusionBegin.tagNumber)) { + if (fieldInfoFusionBegin != null && + msg.hasField(fieldInfoFusionBegin.tagNumber)) { break; } @@ -878,16 +840,14 @@ static double nextDoubleNonZero(Random rng) { // Define the bool variable - - var fieldInfo = msg.info_.byName["tierstatusupdate"]; if (fieldInfo == null) { - throw FusionError('Expected field not found in message: tierstatusupdate'); + throw FusionError( + 'Expected field not found in message: tierstatusupdate'); } bool messageIsTierStatusUpdate = msg.hasField(fieldInfo.tagNumber); - if (!messageIsTierStatusUpdate) { throw FusionError('Expected a TierStatusUpdate message'); } @@ -895,11 +855,11 @@ static double nextDoubleNonZero(Random rng) { late var statuses; if (messageIsTierStatusUpdate) { //TierStatusUpdate tierStatusUpdate = msg.tierstatusupdate; - var tierStatusUpdate = msg.getField(fieldInfo.tagNumber) as TierStatusUpdate; + var tierStatusUpdate = + msg.getField(fieldInfo.tagNumber) as TierStatusUpdate; statuses = tierStatusUpdate.statuses; } - double maxfraction = 0.0; var maxtiers = []; int? besttime; @@ -929,7 +889,8 @@ static double nextDoubleNonZero(Random rng) { if (statuses.containsKey(tier)) { var tierStr = tiersStrings[tier]; if (tierStr == null) { - throw FusionError('server reported status on tier we are not registered for'); + throw FusionError( + 'server reported status on tier we are not registered for'); } if (tier == besttimetier) { displayBest.insert(0, '**$tierStr**'); @@ -959,11 +920,14 @@ static double nextDoubleNonZero(Random rng) { } if (besttime != null) { - status = Tuple('waiting', 'Starting in ${besttime}s. $tiersString'); + status = Tuple( + 'waiting', 'Starting in ${besttime}s. $tiersString'); } else if (maxfraction >= 1) { - status = Tuple('waiting', 'Starting soon. $tiersString'); + status = + Tuple('waiting', 'Starting soon. $tiersString'); } else if (displayBest.isNotEmpty || displayMid.isNotEmpty) { - status = Tuple('waiting', '${(maxfraction * 100).round()}% full. $tiersString'); + status = Tuple( + 'waiting', '${(maxfraction * 100).round()}% full. $tiersString'); } else { status = Tuple('waiting', tiersString); } @@ -979,15 +943,13 @@ static double nextDoubleNonZero(Random rng) { throw FusionError('Expected a FusionBegin message'); } + t_fusionBegin = DateTime.now(); - - - t_fusionBegin = DateTime.now(); - - - var clockMismatch = msg.serverTime - DateTime.now().millisecondsSinceEpoch / 1000; + var clockMismatch = + msg.serverTime - DateTime.now().millisecondsSinceEpoch / 1000; if (clockMismatch.abs() > Protocol.MAX_CLOCK_DISCREPANCY) { - throw FusionError("Clock mismatch too large: ${clockMismatch.toStringAsFixed(3)}."); + throw FusionError( + "Clock mismatch too large: ${clockMismatch.toStringAsFixed(3)}."); } tier = msg.tier; @@ -999,17 +961,21 @@ static double nextDoubleNonZero(Random rng) { covertSSL = msg.covertSSL; beginTime = msg.serverTime; - lastHash = Util.calcInitialHash(tier, covertDomainB, covertPort, covertSSL, beginTime); + lastHash = Util.calcInitialHash( + tier, covertDomainB, covertPort, covertSSL, beginTime); var outAmounts = tierOutputs[tier]; var outAddrs = Util.reserve_change_addresses(outAmounts?.length ?? 0); reservedAddresses = outAddrs; - outputs = Util.zip(outAmounts ?? [], outAddrs).map((pair) => Output(value: pair[0], addr: pair[1])).toList(); + outputs = Util.zip(outAmounts ?? [], outAddrs) + .map((pair) => Output(value: pair[0], addr: pair[1])) + .toList(); safetyExcessFee = safety_exess_fees[tier] ?? 0; - print("starting fusion rounds at tier $tier: ${coins.length} inputs and ${outputs.length} outputs"); + print( + "starting fusion rounds at tier $tier: ${coins.length} inputs and ${outputs.length} outputs"); } Future startCovert() async { @@ -1029,27 +995,27 @@ static double nextDoubleNonZero(Random rng) { tor_port, numComponents, Protocol.COVERT_SUBMIT_WINDOW, - Protocol.COVERT_SUBMIT_TIMEOUT - ); + Protocol.COVERT_SUBMIT_TIMEOUT); try { - covert.scheduleConnections( - t_fusionBegin, + covert.scheduleConnections(t_fusionBegin, Duration(seconds: Protocol.COVERT_CONNECT_WINDOW.toInt()), numSpares: Protocol.COVERT_CONNECT_SPARES.toInt(), - connectTimeout: Protocol.COVERT_CONNECT_TIMEOUT.toInt() - ); - + connectTimeout: Protocol.COVERT_CONNECT_TIMEOUT.toInt()); // loop until a just a bit before we're expecting startRound, watching for status updates - final tend = t_fusionBegin.add(Duration(seconds: (Protocol.WARMUP_TIME - Protocol.WARMUP_SLOP - 1).round())); + final tend = t_fusionBegin.add(Duration( + seconds: (Protocol.WARMUP_TIME - Protocol.WARMUP_SLOP - 1).round())); - while (DateTime.now().millisecondsSinceEpoch / 1000 < tend.millisecondsSinceEpoch / 1000) { + while (DateTime.now().millisecondsSinceEpoch / 1000 < + tend.millisecondsSinceEpoch / 1000) { + int numConnected = + covert.slots.where((s) => s.covConn?.connection != null).length; - int numConnected = covert.slots.where((s) => s.covConn?.connection != null).length; + int numSpareConnected = + covert.spareConnections.where((c) => c.connection != null).length; - int numSpareConnected = covert.spareConnections.where((c) => c.connection != null).length; - - status = Tuple('running', 'Setting up Tor connections ($numConnected+$numSpareConnected out of $numComponents)'); + status = Tuple('running', + 'Setting up Tor connections ($numConnected+$numSpareConnected out of $numComponents)'); await Future.delayed(Duration(seconds: 1)); @@ -1065,37 +1031,48 @@ static double nextDoubleNonZero(Random rng) { return covert; } - void runRound(CovertSubmitter covert) async { status = Tuple('running', 'Starting round ${roundcount.toString()}'); - int timeoutInSeconds = (2 * Protocol.WARMUP_SLOP + Protocol.STANDARD_TIMEOUT).toInt(); - var msg = await recv(['startround'], timeout: Duration(seconds: timeoutInSeconds)); + int timeoutInSeconds = + (2 * Protocol.WARMUP_SLOP + Protocol.STANDARD_TIMEOUT).toInt(); + var msg = await recv(['startround'], + timeout: Duration(seconds: timeoutInSeconds)); // Record the time we got this message; it forms the basis time for all covert activities. final covertT0 = DateTime.now().millisecondsSinceEpoch / 1000; - double covertClock() => (DateTime.now().millisecondsSinceEpoch / 1000) - covertT0; + double covertClock() => + (DateTime.now().millisecondsSinceEpoch / 1000) - covertT0; final roundTime = (msg as StartRound).serverTime; // Check the server's declared unix time, which will be committed. - final clockMismatch = (msg as StartRound).serverTime - DateTime.now().millisecondsSinceEpoch / 1000; + final clockMismatch = (msg as StartRound).serverTime - + DateTime.now().millisecondsSinceEpoch / 1000; if (clockMismatch.abs() > Protocol.MAX_CLOCK_DISCREPANCY) { - throw FusionError("Clock mismatch too large: ${clockMismatch.toInt().toStringAsPrecision(3)}."); + throw FusionError( + "Clock mismatch too large: ${clockMismatch.toInt().toStringAsPrecision(3)}."); } if (t_fusionBegin != null) { // On the first startround message, check that the warmup time was within acceptable bounds. - final lag = covertT0 - (t_fusionBegin.millisecondsSinceEpoch / 1000) - Protocol.WARMUP_TIME; + final lag = covertT0 - + (t_fusionBegin.millisecondsSinceEpoch / 1000) - + Protocol.WARMUP_TIME; if (lag.abs() > Protocol.WARMUP_SLOP) { - throw FusionError("Warmup period too different from expectation (|${lag.toStringAsFixed(3)}s| > ${Protocol.WARMUP_SLOP.toStringAsFixed(3)}s)."); + throw FusionError( + "Warmup period too different from expectation (|${lag.toStringAsFixed(3)}s| > ${Protocol.WARMUP_SLOP.toStringAsFixed(3)}s)."); } t_fusionBegin = DateTime.now(); } print("round starting at ${DateTime.now().millisecondsSinceEpoch / 1000}"); - final inputFees = coins.map((e) => Util.componentFee(e.sizeOfInput(), componentFeeRate.toInt())).reduce((a, b) => a + b); - final outputFees = outputs.length * Util.componentFee(34, componentFeeRate.toInt()); + final inputFees = coins + .map( + (e) => Util.componentFee(e.sizeOfInput(), componentFeeRate.toInt())) + .reduce((a, b) => a + b); + final outputFees = + outputs.length * Util.componentFee(34, componentFeeRate.toInt()); final sumIn = coins.map((e) => e.amount).reduce((a, b) => a + b); final sumOut = outputs.map((e) => e.value).reduce((a, b) => a + b); @@ -1110,7 +1087,8 @@ static double nextDoubleNonZero(Random rng) { ]; if (!safeties.every((element) => element)) { - throw Exception("(BUG!) Funds re-check failed -- aborting for safety. ${safeties.toString()}"); + throw Exception( + "(BUG!) Funds re-check failed -- aborting for safety. ${safeties.toString()}"); } final roundPubKey = (msg as StartRound).roundPubkey; @@ -1121,15 +1099,18 @@ static double nextDoubleNonZero(Random rng) { } final numBlanks = numComponents - coins.length - outputs.length; - final List genComponentsResults = genComponents(numBlanks, coins, outputs, componentFeeRate.toInt()); + final List genComponentsResults = + genComponents(numBlanks, coins, outputs, componentFeeRate.toInt()); final List myCommitments = []; final List myComponentSlots = []; final List myComponents = []; final List myProofs = []; final List privKeys = []; - final List pedersenAmount = []; // replace dynamic with the actual type - final List pedersenNonce = []; // replace dynamic with the actual type + final List pedersenAmount = + []; // replace dynamic with the actual type + final List pedersenNonce = + []; // replace dynamic with the actual type for (var genComponentResult in genComponentsResults) { myCommitments.add(genComponentResult.commitment); @@ -1140,7 +1121,9 @@ static double nextDoubleNonZero(Random rng) { pedersenAmount.add(genComponentResult.pedersenAmount); pedersenNonce.add(genComponentResult.pedersenNonce); } - assert(excessFee == pedersenAmount.reduce((a, b) => a + b)); // sanity check that we didn't mess up the above + assert(excessFee == + pedersenAmount.reduce( + (a, b) => a + b)); // sanity check that we didn't mess up the above assert(myComponents.toSet().length == myComponents.length); // no duplicates // Need to implement this! schnorr is from EC schnorr.py @@ -1155,18 +1138,17 @@ static double nextDoubleNonZero(Random rng) { check_stop(); check_coins(); - await send(PlayerCommit( initialCommitments: myCommitments, excessFee: Int64(excessFee), pedersenTotalNonce: pedersenNonce.cast(), randomNumberCommitment: sha256.convert(randomNumber).bytes, - blindSigRequests: blindSigRequests.map((r) => r.getRequest() as List).toList(), + blindSigRequests: + blindSigRequests.map((r) => r.getRequest() as List).toList(), )); - - msg = await recv(['blindsigresponses'], timeout: Duration(seconds: Protocol.T_START_COMPS.toInt())); - + msg = await recv(['blindsigresponses'], + timeout: Duration(seconds: Protocol.T_START_COMPS.toInt())); if (msg is BlindSigResponses) { var typedMsg = msg as BlindSigResponses; @@ -1178,10 +1160,11 @@ static double nextDoubleNonZero(Random rng) { final blindSigs = List.generate( blindSigRequests.length, - (index) { + (index) { if (msg is BlindSigResponses) { var typedMsg = msg as BlindSigResponses; - return blindSigRequests[index].finalize(typedMsg.scalars[index], check: true); + return blindSigRequests[index] + .finalize(typedMsg.scalars[index], check: true); } else { // Handle the case where msg is not of type BlindSigResponses throw Exception('Unexpected message type: ${msg.runtimeType}'); @@ -1189,7 +1172,6 @@ static double nextDoubleNonZero(Random rng) { }, ); - // Sleep until the covert component phase really starts, to catch covert connection failures. var remainingTime = Protocol.T_START_COMPS - covertClock(); if (remainingTime < 0) { @@ -1214,7 +1196,6 @@ static double nextDoubleNonZero(Random rng) { // provided our input components then it would be a leak to have them all drop at once. covert.setStopTime((covertT0 + Protocol.T_START_CLOSE).toInt()); - // Schedule covert submissions. List messages = List.filled(myComponents.length, null); @@ -1222,34 +1203,35 @@ static double nextDoubleNonZero(Random rng) { messages[myComponentSlots[i]] = CovertComponent( roundPubkey: roundPubKey, signature: blindSigs[i], - component: myComponents[i] - ); + component: myComponents[i]); } if (messages.any((element) => element == null)) { throw FusionError('Messages list includes null values.'); } - final targetDateTime = DateTime.fromMillisecondsSinceEpoch(((covertT0 + Protocol.T_START_COMPS) * 1000).toInt()); + final targetDateTime = DateTime.fromMillisecondsSinceEpoch( + ((covertT0 + Protocol.T_START_COMPS) * 1000).toInt()); covert.scheduleSubmissions(targetDateTime, messages); - // While submitting, we download the (large) full commitment list. - msg = await recv(['allcommitments'], timeout: Duration(seconds: Protocol.T_START_SIGS.toInt())); + msg = await recv(['allcommitments'], + timeout: Duration(seconds: Protocol.T_START_SIGS.toInt())); AllCommitments allCommitmentsMsg = msg as AllCommitments; - List allCommitments = allCommitmentsMsg.initialCommitments.map((commitmentBytes) { + List allCommitments = + allCommitmentsMsg.initialCommitments.map((commitmentBytes) { return InitialCommitment.fromBuffer(commitmentBytes); }).toList(); - // Quick check on the commitment list. if (allCommitments.toSet().length != allCommitments.length) { throw FusionError('Commitments list includes duplicates.'); } try { - List allCommitmentsBytes = allCommitments.map((commitment) => commitment.writeToBuffer()).toList(); - myCommitmentIndexes = myCommitments.map((c) => allCommitmentsBytes.indexOf(c)).toList(); - - + List allCommitmentsBytes = allCommitments + .map((commitment) => commitment.writeToBuffer()) + .toList(); + myCommitmentIndexes = + myCommitments.map((c) => allCommitmentsBytes.indexOf(c)).toList(); } on Exception { throw FusionError('One or more of my commitments missing.'); } @@ -1260,9 +1242,11 @@ static double nextDoubleNonZero(Random rng) { } // Once all components are received, the server shares them with us: - msg = await recv(['sharecovertcomponents'], timeout: Duration(seconds: Protocol.T_START_SIGS.toInt())); + msg = await recv(['sharecovertcomponents'], + timeout: Duration(seconds: Protocol.T_START_SIGS.toInt())); - ShareCovertComponents shareCovertComponentsMsg = msg as ShareCovertComponents; + ShareCovertComponents shareCovertComponentsMsg = + msg as ShareCovertComponents; List> allComponents = shareCovertComponentsMsg.components; bool skipSignatures = msg.getField(2); @@ -1274,7 +1258,10 @@ static double nextDoubleNonZero(Random rng) { covert.checkDone(); try { - myComponentIndexes = myComponents.map((c) => allComponents.indexWhere((element) => ListEquality().equals(element, c))).toList(); + myComponentIndexes = myComponents + .map((c) => allComponents + .indexWhere((element) => ListEquality().equals(element, c))) + .toList(); if (myComponentIndexes.contains(-1)) { throw FusionError('One or more of my components missing.'); } @@ -1285,12 +1272,15 @@ static double nextDoubleNonZero(Random rng) { // Need to implement: check the components list and see if there are enough inputs/outputs // for there to be significant privacy. - List> allCommitmentsBytes = allCommitments.map((commitment) => commitment.writeToBuffer().toList()).toList(); - List sessionHash = Util.calcRoundHash(lastHash, roundPubKey, roundTime.toInt(), allCommitmentsBytes, allComponents); - - - if (shareCovertComponentsMsg.sessionHash != null && !ListEquality().equals(shareCovertComponentsMsg.sessionHash, sessionHash)) { + List> allCommitmentsBytes = allCommitments + .map((commitment) => commitment.writeToBuffer().toList()) + .toList(); + List sessionHash = Util.calcRoundHash(lastHash, roundPubKey, + roundTime.toInt(), allCommitmentsBytes, allComponents); + if (shareCovertComponentsMsg.sessionHash != null && + !ListEquality() + .equals(shareCovertComponentsMsg.sessionHash, sessionHash)) { throw FusionError('Session hash mismatch (bug!)'); } @@ -1298,33 +1288,25 @@ static double nextDoubleNonZero(Random rng) { print("starting covert signature submission"); status = Tuple('running', 'covert submission: signatures'); - if (allComponents - .toSet() - .length != allComponents.length) { + if (allComponents.toSet().length != allComponents.length) { throw FusionError('Server component list includes duplicates.'); } - - var txInputIndices = Transaction.txFromComponents( - allComponents, sessionHash); - + var txInputIndices = + Transaction.txFromComponents(allComponents, sessionHash); Tuple txData = Transaction.txFromComponents(allComponents, sessionHash); tx = txData.item1; List inputIndices = txData.item2; - - List< - CovertTransactionSignature?> covertTransactionSignatureMessages = List< - CovertTransactionSignature?>.filled(myComponents.length, null); + List covertTransactionSignatureMessages = + List.filled(myComponents.length, null); var my_combined = List>.generate( inputIndices.length, - (index) => Tuple(inputIndices[index], tx.Inputs[index]), + (index) => Tuple(inputIndices[index], tx.Inputs[index]), ); - - for (var i = 0; i < my_combined.length; i++) { int cIdx = my_combined[i].item1; Input inp = my_combined[i].item2; @@ -1348,19 +1330,17 @@ static double nextDoubleNonZero(Random rng) { DateTime covertT0DateTime = DateTime.fromMillisecondsSinceEpoch( covertT0.toInt() * 1000); // covertT0 is in seconds covert.scheduleSubmissions( - covertT0DateTime.add( - Duration(milliseconds: Protocol.T_START_SIGS.toInt())), - covertTransactionSignatureMessages - ); - + covertT0DateTime + .add(Duration(milliseconds: Protocol.T_START_SIGS.toInt())), + covertTransactionSignatureMessages); // wait for result int timeoutMillis = (Protocol.T_EXPECTING_CONCLUSION - - Protocol.TS_EXPECTING_COVERT_COMPONENTS).toInt(); + Protocol.TS_EXPECTING_COVERT_COMPONENTS) + .toInt(); Duration timeout = Duration(milliseconds: timeoutMillis); msg = await recv(['fusionresult'], timeout: timeout); - // Critical check on server's response timing. if (covertClock() > Protocol.T_EXPECTING_CONCLUSION) { throw FusionError('Fusion result message arrived too slowly.'); @@ -1389,44 +1369,37 @@ static double nextDoubleNonZero(Random rng) { String txHex = tx.serialize(); txid = tx.txid(); - String sumInStr = Util.formatSatoshis(sumIn, - numZeros: 8); - String feeStr = totalFee - .toString(); + String sumInStr = Util.formatSatoshis(sumIn, numZeros: 8); + String feeStr = totalFee.toString(); String feeLoc = 'fee'; - String label = "CashFusion ${coins.length}⇢${outputs - .length}, ${sumInStr} BCH (−${feeStr} sats ${feeLoc})"; + String label = + "CashFusion ${coins.length}⇢${outputs.length}, ${sumInStr} BCH (−${feeStr} sats ${feeLoc})"; Util.updateWalletLabel(txid, label); - } - - else { - badComponents = msg.badComponents.toSet(); + } else { + badComponents = msg.badComponents.toSet(); if (badComponents.intersection(myComponentIndexes.toSet()).isNotEmpty) { - print("bad components: ${badComponents.toList()} mine: ${myComponentIndexes.toList()}"); + print( + "bad components: ${badComponents.toList()} mine: ${myComponentIndexes.toList()}"); throw FusionError("server thinks one of my components is bad!"); } } - - - } - else { // skip_signatures True - Set badComponents = Set(); + } else { + // skip_signatures True + Set badComponents = Set(); } // ### Blame phase ### - covert.setStopTime((covertT0 + Protocol.T_START_CLOSE_BLAME).floor()); print("sending proofs"); status = Tuple('running', 'round failed - sending proofs'); - // create a list of commitment indexes, but leaving out mine. List othersCommitmentIdxes = []; - for(int i=0; i dstCommits = []; - for(int i=0; i encproofs = List.filled(myCommitments.length, ''); - ECDomainParameters params = ECDomainParameters('secp256k1'); - for (int i=0; i encodedEncproofs = encproofs.map((e) => Uint8List.fromList(e.codeUnits)).toList(); - this.send(MyProofsList(encryptedProofs: encodedEncproofs, randomNumber: randomNumber)); + List encodedEncproofs = + encproofs.map((e) => Uint8List.fromList(e.codeUnits)).toList(); + this.send(MyProofsList( + encryptedProofs: encodedEncproofs, randomNumber: randomNumber)); status = Tuple('running', 'round failed - checking proofs'); print("receiving proofs"); - msg = await this.recv(['theirproofslist'], timeout: Duration(seconds: (2 * Protocol.STANDARD_TIMEOUT).round())); + msg = await this.recv(['theirproofslist'], + timeout: Duration(seconds: (2 * Protocol.STANDARD_TIMEOUT).round())); List blames = []; @@ -1498,22 +1478,22 @@ static double nextDoubleNonZero(Random rng) { var sKey; var proofBlob; - - try { - var result = await decrypt(Uint8List.fromList(rp.encryptedProof), privKey); - proofBlob = result.item1; // First item is the decrypted data - sKey = result.item2; // Second item is the symmetric key + var result = + await decrypt(Uint8List.fromList(rp.encryptedProof), privKey); + proofBlob = result.item1; // First item is the decrypted data + sKey = result.item2; // Second item is the symmetric key } on Exception catch (e) { print("found an undecryptable proof"); - blames.add(Blames_BlameProof(whichProof: i, privkey: privKey, blameReason: 'undecryptable')); + blames.add(Blames_BlameProof( + whichProof: i, privkey: privKey, blameReason: 'undecryptable')); continue; } - var commitment = InitialCommitment(); try { - commitment.mergeFromBuffer(commitmentBlob); // Method to parse protobuf data + commitment + .mergeFromBuffer(commitmentBlob); // Method to parse protobuf data } on FormatException catch (e) { throw FusionError("Server relayed bad commitment"); } @@ -1522,13 +1502,17 @@ static double nextDoubleNonZero(Random rng) { try { // Convert allComponents to List - List allComponentsUint8 = allComponents.map((component) => Uint8List.fromList(component)).toList(); + List allComponentsUint8 = allComponents + .map((component) => Uint8List.fromList(component)) + .toList(); // Convert badComponents to List List badComponentsList = badComponents.toList(); // Convert componentFeeRate to int if it's double - int componentFeerateInt = componentFeeRate.round(); // or use .toInt() if you want to truncate instead of rounding + int componentFeerateInt = componentFeeRate + .round(); // or use .toInt() if you want to truncate instead of rounding - var inpComp = validateProofInternal(proofBlob, commitment, allComponentsUint8, badComponentsList, componentFeerateInt); + var inpComp = validateProofInternal(proofBlob, commitment, + allComponentsUint8, badComponentsList, componentFeerateInt); } on Exception catch (e) { print("found an erroneous proof: ${e.toString()}"); var blameProof = Blames_BlameProof(); @@ -1539,27 +1523,26 @@ static double nextDoubleNonZero(Random rng) { continue; } - if (inpComp != null) { countInputs++; try { Util.checkInputElectrumX(inpComp); } on Exception catch (e) { - print("found a bad input [${rp.srcCommitmentIdx}]: $e (${inpComp.prevTxid.reversed.toList().toHex()}:${inpComp.prevIndex})"); + print( + "found a bad input [${rp.srcCommitmentIdx}]: $e (${inpComp.prevTxid.reversed.toList().toHex()}:${inpComp.prevIndex})"); var blameProof = Blames_BlameProof(); blameProof.whichProof = i; blameProof.sessionKey = sKey; - blameProof.blameReason = 'input does not match blockchain: ' + e.toString(); + blameProof.blameReason = + 'input does not match blockchain: ' + e.toString(); blameProof.needLookupBlockchain = true; blames.add(blameProof); - } catch (e) { - print("verified an input internally, but was unable to check it against blockchain: ${e}"); + print( + "verified an input internally, but was unable to check it against blockchain: ${e}"); } } - - } print("checked ${msg.proofs.length} proofs, $countInputs of them inputs"); @@ -1571,13 +1554,10 @@ static double nextDoubleNonZero(Random rng) { // Await the final 'restartround' message. It might take some time // to arrive since other players might be slow, and then the server // itself needs to check blockchain. - await recv(['restartround'], timeout: Duration(seconds: 2 * (Protocol.STANDARD_TIMEOUT.round() + Protocol.BLAME_VERIFY_TIME.round()))); - - - } // end of run_round() function. - - - - + await recv(['restartround'], + timeout: Duration( + seconds: 2 * + (Protocol.STANDARD_TIMEOUT.round() + + Protocol.BLAME_VERIFY_TIME.round()))); + } // end of run_round() function. } // END OF CLASS - diff --git a/lib/services/cashfusion/main.dart b/lib/services/cashfusion/main.dart deleted file mode 100644 index 0f6218bbb..000000000 --- a/lib/services/cashfusion/main.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:flutter/material.dart'; -import 'fusion.dart'; -import 'pedersen.dart'; -import 'encrypt.dart'; -import 'validation.dart'; - -void main() { - - Fusion.foo(); - - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } -} diff --git a/lib/services/cashfusion/pedersen.dart b/lib/services/cashfusion/pedersen.dart index df9bbfc18..d15ff7800 100644 --- a/lib/services/cashfusion/pedersen.dart +++ b/lib/services/cashfusion/pedersen.dart @@ -1,8 +1,8 @@ -import 'package:pointycastle/ecc/api.dart'; -import 'util.dart'; -import 'dart:math'; import 'dart:typed_data'; +import 'package:pointycastle/ecc/api.dart'; +import 'package:stackwallet/services/cashfusion/util.dart'; + ECDomainParameters getDefaultParams() { return ECDomainParameters("secp256k1"); } @@ -11,10 +11,10 @@ class NullPointError implements Exception { String errMsg() => 'NullPointError: Either Hpoint or HGpoint is null.'; } - class NonceRangeError implements Exception { final String message; - NonceRangeError([this.message = "Nonce value must be in the range 0 < nonce < order"]); + NonceRangeError( + [this.message = "Nonce value must be in the range 0 < nonce < order"]); String toString() => "NonceRangeError: $message"; } @@ -26,12 +26,12 @@ class ResultAtInfinity implements Exception { class InsecureHPoint implements Exception { final String message; - InsecureHPoint([this.message = "The H point has a known discrete logarithm, which means the commitment setup is broken"]); + InsecureHPoint( + [this.message = + "The H point has a known discrete logarithm, which means the commitment setup is broken"]); String toString() => "InsecureHPoint: $message"; } - - class PedersenSetup { late ECPoint _H; late ECPoint _HG; @@ -53,7 +53,6 @@ class PedersenSetup { Commitment commit(BigInt amount, {BigInt? nonce, Uint8List? PUncompressed}) { return Commitment(this, amount, nonce: nonce, PUncompressed: PUncompressed); } - } class Commitment { @@ -62,8 +61,8 @@ class Commitment { late BigInt nonce; late Uint8List PUncompressed; - - Commitment(this.setup, BigInt amount, {BigInt? nonce, Uint8List? PUncompressed}) { + Commitment(this.setup, BigInt amount, + {BigInt? nonce, Uint8List? PUncompressed}) { this.nonce = nonce ?? Util.secureRandomBigInt(setup.params.n.bitLength); amountMod = amount % setup.params.n; @@ -84,13 +83,16 @@ class Commitment { ECPoint? HpointMultiplied = Hpoint * multiplier1; ECPoint? HGpointMultiplied = HGpoint * multiplier2; - ECPoint? Ppoint = HpointMultiplied != null && HGpointMultiplied != null ? HpointMultiplied + HGpointMultiplied : null; + ECPoint? Ppoint = HpointMultiplied != null && HGpointMultiplied != null + ? HpointMultiplied + HGpointMultiplied + : null; if (Ppoint == setup.params.curve.infinity) { throw ResultAtInfinity(); } - this.PUncompressed = PUncompressed ?? Ppoint?.getEncoded(false) ?? Uint8List(0); + this.PUncompressed = + PUncompressed ?? Ppoint?.getEncoded(false) ?? Uint8List(0); } void calcInitial(PedersenSetup setup, BigInt amount) { @@ -114,7 +116,9 @@ class Commitment { ECPoint? HpointMultiplied = Hpoint * multiplier1; ECPoint? HGpointMultiplied = HGpoint * multiplier2; - ECPoint? Ppoint = HpointMultiplied != null && HGpointMultiplied != null ? HpointMultiplied + HGpointMultiplied : null; + ECPoint? Ppoint = HpointMultiplied != null && HGpointMultiplied != null + ? HpointMultiplied + HGpointMultiplied + : null; if (Ppoint == setup.params.curve.infinity) { throw ResultAtInfinity(); @@ -124,14 +128,17 @@ class Commitment { } static Uint8List add_points(Iterable pointsIterable) { - ECDomainParameters params = getDefaultParams(); // Using helper function here - var pointList = pointsIterable.map((pser) => Util.ser_to_point(pser, params)).toList(); + ECDomainParameters params = + getDefaultParams(); // Using helper function here + var pointList = + pointsIterable.map((pser) => Util.ser_to_point(pser, params)).toList(); if (pointList.isEmpty) { throw ArgumentError('Empty list'); } - ECPoint pSum = pointList.first; // Initialize pSum with the first point in the list + ECPoint pSum = + pointList.first; // Initialize pSum with the first point in the list for (var i = 1; i < pointList.length; i++) { pSum = (pSum + pointList[i])!; @@ -144,7 +151,6 @@ class Commitment { return Util.point_to_ser(pSum, false); } - Commitment addCommitments(Iterable commitmentIterable) { BigInt ktotal = BigInt.zero; // Changed to BigInt from int BigInt atotal = BigInt.zero; // Changed to BigInt from int @@ -168,7 +174,8 @@ class Commitment { ktotal = ktotal % setup.params.n; // Changed order to setup.params.n - if (ktotal == BigInt.zero) { // Changed comparison from 0 to BigInt.zero + if (ktotal == BigInt.zero) { + // Changed comparison from 0 to BigInt.zero throw Exception('Nonce range error'); } @@ -182,6 +189,7 @@ class Commitment { } else { PUncompressed = null; } - return Commitment(setup, atotal, nonce: ktotal, PUncompressed: PUncompressed); + return Commitment(setup, atotal, + nonce: ktotal, PUncompressed: PUncompressed); } } diff --git a/lib/services/cashfusion/protocol.dart b/lib/services/cashfusion/protocol.dart index fa72ce7a5..65abb965c 100644 --- a/lib/services/cashfusion/protocol.dart +++ b/lib/services/cashfusion/protocol.dart @@ -1,11 +1,8 @@ - - class Protocol { static const VERSION = 'alpha13'; static const FUSE_ID = 'FUZ\x00'; - // Safety limits to prevent loss of funds / limit fees: //(Note that if we enter multiply into the same fusion, our limits apply //separately for each "player".) @@ -56,4 +53,3 @@ class Protocol { static const STANDARD_TIMEOUT = 3.0; static const BLAME_VERIFY_TIME = 5.0; } - diff --git a/lib/services/cashfusion/socketwrapper.dart b/lib/services/cashfusion/socketwrapper.dart index 54b8a105e..0a20670ab 100644 --- a/lib/services/cashfusion/socketwrapper.dart +++ b/lib/services/cashfusion/socketwrapper.dart @@ -5,16 +5,19 @@ class SocketWrapper { final String serverIP; final int serverPort; - late Stream> _receiveStream; // create a field for the broadcast stream + late Stream> + _receiveStream; // create a field for the broadcast stream SocketWrapper(this.serverIP, this.serverPort); Socket get socket => _socket; - Stream> get receiveStream => _receiveStream; // expose the stream with a getter + Stream> get receiveStream => + _receiveStream; // expose the stream with a getter Future connect() async { _socket = await Socket.connect(serverIP, serverPort); - _receiveStream = _socket.asBroadcastStream(); // initialize the broadcast stream + _receiveStream = + _socket.asBroadcastStream(); // initialize the broadcast stream _socket.done.then((_) { print('......Socket has been closed'); }); @@ -25,7 +28,8 @@ class SocketWrapper { void status() { if (_socket != null) { - print("Socket connected to ${_socket.remoteAddress.address}:${_socket.remotePort}"); + print( + "Socket connected to ${_socket.remoteAddress.address}:${_socket.remotePort}"); } else { print("Socket is not connected"); } diff --git a/lib/services/cashfusion/util.dart b/lib/services/cashfusion/util.dart index cb8e5f621..f8cf75404 100644 --- a/lib/services/cashfusion/util.dart +++ b/lib/services/cashfusion/util.dart @@ -1,24 +1,24 @@ - -import 'package:stackwallet/services/cashfusion/fusion.dart'; -import 'package:pointycastle/ecc/api.dart'; +import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; -import 'dart:convert'; + import 'package:crypto/crypto.dart' as crypto; -import 'protocol.dart'; +import 'package:pointycastle/ecc/api.dart'; + import 'fusion.pb.dart'; -import 'dart:convert'; +import 'protocol.dart'; class Address { String addr = ""; - Address({required this.addr}); // Constructor updated to accept addr as a named parameter + Address( + {required this.addr}); // Constructor updated to accept addr as a named parameter Address._create({required this.addr}); static Address fromScriptPubKey(List scriptPubKey) { // This is just a placeholder code - String addr = ""; // This should be computed from the scriptPubKey + String addr = ""; // This should be computed from the scriptPubKey return Address(addr: addr); } @@ -32,7 +32,6 @@ class Address { } } - class Tuple { T1 item1; T2 item2; @@ -49,8 +48,6 @@ class Tuple { } class Util { - - static Uint8List hexToBytes(String hex) { var result = new Uint8List(hex.length ~/ 2); for (var i = 0; i < hex.length; i += 2) { @@ -63,8 +60,7 @@ class Util { static void checkInputElectrumX(InputComponent inputComponent) { // Implementation needed here // - } - + } static int randPosition(Uint8List seed, int numPositions, int counter) { // counter to bytes @@ -77,7 +73,8 @@ class Util { // take the first 8 bytes var first8Bytes = digest.bytes.take(8).toList(); - var int64 = ByteData.sublistView(Uint8List.fromList(first8Bytes)).getUint64(0, Endian.big); + var int64 = ByteData.sublistView(Uint8List.fromList(first8Bytes)) + .getUint64(0, Endian.big); // perform the modulo operation return ((int64 * numPositions) >> 64).toInt(); @@ -93,7 +90,6 @@ class Util { return 500; } - static Address getAddressFromOutputScript(Uint8List scriptpubkey) { // Dummy implementation... @@ -102,23 +98,22 @@ class Util { return Address.fromString('dummy_address'); } - - static bool schnorrVerify(ECPoint pubkey, List signature, Uint8List messageHash) { + static bool schnorrVerify( + ECPoint pubkey, List signature, Uint8List messageHash) { // Implementation needed: actual Schnorr signature verification return true; } - - static String formatSatoshis(sats, {int numZeros=8}) { + static String formatSatoshis(sats, {int numZeros = 8}) { // To implement return ""; } - static void updateWalletLabel(String txid, String label) { + static void updateWalletLabel(String txid, String label) { // Call the wallet layer. } - static Uint8List getRandomBytes(int length) { + static Uint8List getRandomBytes(int length) { final rand = Random.secure(); final bytes = Uint8List(length); for (int i = 0; i < length; i++) { @@ -127,17 +122,18 @@ class Util { return bytes; } -static List> zip(List list1, List list2) { + static List> zip(List list1, List list2) { int length = min(list1.length, list2.length); return List>.generate(length, (i) => [list1[i], list2[i]]); } - - static List calcInitialHash(int tier, Uint8List covertDomainB, int covertPort, bool covertSsl, double beginTime) { + static List calcInitialHash(int tier, Uint8List covertDomainB, + int covertPort, bool covertSsl, double beginTime) { // Converting int to bytes in BigEndian order var tierBytes = ByteData(8)..setInt64(0, tier, Endian.big); var covertPortBytes = ByteData(4)..setInt32(0, covertPort, Endian.big); - var beginTimeBytes = ByteData(8)..setInt64(0, beginTime.toInt(), Endian.big); + var beginTimeBytes = ByteData(8) + ..setInt64(0, beginTime.toInt(), Endian.big); // Define constants const version = Protocol.VERSION; @@ -159,16 +155,21 @@ static List> zip(List list1, List list2) { return digest.bytes; } -static List calcRoundHash(List lastHash, List roundPubkey, int roundTime, List> allCommitments, List> allComponents) { - return listHash([ - utf8.encode('Cash Fusion Round'), - lastHash, - roundPubkey, - bigIntToBytes(BigInt.from(roundTime)), - listHash(allCommitments), - listHash(allComponents), - ]); -} + static List calcRoundHash( + List lastHash, + List roundPubkey, + int roundTime, + List> allCommitments, + List> allComponents) { + return listHash([ + utf8.encode('Cash Fusion Round'), + lastHash, + roundPubkey, + bigIntToBytes(BigInt.from(roundTime)), + listHash(allCommitments), + listHash(allComponents), + ]); + } static List listHash(Iterable> iterable) { var bytes = []; @@ -179,12 +180,11 @@ static List calcRoundHash(List lastHash, List roundPubkey, int ro bytes.addAll(x); } return crypto.sha256.convert(bytes).bytes; - } - static Uint8List get_current_genesis_hash() { - var GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; + var GENESIS = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; var _lastGenesisHash = hexToBytes(GENESIS).reversed.toList(); return Uint8List.fromList(_lastGenesisHash); } @@ -194,7 +194,6 @@ static List calcRoundHash(List lastHash, List roundPubkey, int ro return []; } - static List
reserve_change_addresses(int number_addresses) { //implement later based on wallet. return []; @@ -205,27 +204,26 @@ static List calcRoundHash(List lastHash, List roundPubkey, int ro return true; } -static Uint8List bigIntToBytes(BigInt bigInt) { - return Uint8List.fromList(bigInt.toRadixString(16).padLeft(32, '0').codeUnits); + static Uint8List bigIntToBytes(BigInt bigInt) { + return Uint8List.fromList( + bigInt.toRadixString(16).padLeft(32, '0').codeUnits); } -static Tuple genKeypair() { - var params = ECDomainParameters('secp256k1'); - var privKeyBigInt = _generatePrivateKey(params.n.bitLength); - var pubKeyPoint = params.G * privKeyBigInt; + static Tuple genKeypair() { + var params = ECDomainParameters('secp256k1'); + var privKeyBigInt = _generatePrivateKey(params.n.bitLength); + var pubKeyPoint = params.G * privKeyBigInt; - if (pubKeyPoint == null) { - throw Exception("Error generating public key."); + if (pubKeyPoint == null) { + throw Exception("Error generating public key."); + } + + Uint8List privKey = bigIntToBytes(privKeyBigInt); + Uint8List pubKey = pubKeyPoint.getEncoded(true); + + return Tuple(privKey, pubKey); } - Uint8List privKey = bigIntToBytes(privKeyBigInt); - Uint8List pubKey = pubKeyPoint.getEncoded(true); - - return Tuple(privKey, pubKey); -} - - - // Generates a cryptographically secure private key static BigInt _generatePrivateKey(int bitLength) { final random = Random.secure(); @@ -236,7 +234,9 @@ static Tuple genKeypair() { List rnd = List.generate(bytes, (_) => random.nextInt(256)); var rndBit = random.nextInt(1 << remBit); rnd.add(rndBit); - var privateKey = BigInt.parse(rnd.map((x) => x.toRadixString(16).padLeft(2, '0')).join(), radix: 16); + var privateKey = BigInt.parse( + rnd.map((x) => x.toRadixString(16).padLeft(2, '0')).join(), + radix: 16); return privateKey; } @@ -251,15 +251,16 @@ static Tuple genKeypair() { return BigInt.parse(hexString, radix: 16); } - static Uint8List sha256(Uint8List bytes) { crypto.Digest digest = crypto.sha256.convert(bytes); return Uint8List.fromList(digest.bytes); } + static Uint8List tokenBytes([int nbytes = 32]) { final Random _random = Random.secure(); - return Uint8List.fromList(List.generate(nbytes, (i) => _random.nextInt(256))); + return Uint8List.fromList( + List.generate(nbytes, (i) => _random.nextInt(256))); } static int componentFee(int size, int feerate) { @@ -269,8 +270,8 @@ static Tuple genKeypair() { return ((size * feerate) + 999) ~/ 1000; } - - static ECPoint ser_to_point(Uint8List serializedPoint, ECDomainParameters params) { + static ECPoint ser_to_point( + Uint8List serializedPoint, ECDomainParameters params) { var point = params.curve.decodePoint(serializedPoint); if (point == null) { throw FormatException('Point decoding failed'); @@ -282,7 +283,6 @@ static Tuple genKeypair() { return point.getEncoded(compress); } - static BigInt secureRandomBigInt(int bitLength) { final random = Random.secure(); final bytes = (bitLength + 7) ~/ 8; // ceil division @@ -292,10 +292,13 @@ static Tuple genKeypair() { randomBytes[i] = random.nextInt(256); } - BigInt randomNumber = BigInt.parse(randomBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(), radix: 16); + BigInt randomNumber = BigInt.parse( + randomBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(), + radix: 16); return randomNumber; } - static ECPoint combinePubKeys(List pubKeys) { + + static ECPoint combinePubKeys(List pubKeys) { if (pubKeys.isEmpty) throw ArgumentError('pubKeys cannot be empty'); ECPoint combined = pubKeys.first.curve.infinity!; @@ -310,7 +313,7 @@ static Tuple genKeypair() { return combined; } - static bool isPointOnCurve(ECPoint point, ECCurve curve) { + static bool isPointOnCurve(ECPoint point, ECCurve curve) { var x = point.x!.toBigInteger()!; var y = point.y!.toBigInteger()!; var a = curve.a!.toBigInteger()!; @@ -323,8 +326,4 @@ static Tuple genKeypair() { // Check if the point is on the curve return left == right; } - - - } // END OF CLASS - diff --git a/lib/services/cashfusion/validation.dart b/lib/services/cashfusion/validation.dart index 24555f7d8..95c2a6f5e 100644 --- a/lib/services/cashfusion/validation.dart +++ b/lib/services/cashfusion/validation.dart @@ -1,14 +1,12 @@ -import 'package:protobuf/protobuf.dart'; +import 'dart:typed_data'; + +import 'package:pointycastle/export.dart'; + +import 'encrypt.dart' as Encrypt; +import 'fusion.dart'; import 'fusion.pb.dart' as pb; import 'pedersen.dart'; import 'util.dart'; -import 'encrypt.dart' as Encrypt; -import 'protocol.dart'; -import 'fusion.dart'; -import 'dart:typed_data'; -import 'package:pointycastle/export.dart'; -import 'package:convert/convert.dart'; -import 'pedersen.dart'; class ValidationError implements Exception { final String message; @@ -31,13 +29,12 @@ int componentContrib(pb.Component component, int feerate) { } } - - void check(bool condition, String failMessage) { if (!condition) { throw ValidationError(failMessage); } } + dynamic protoStrictParse(dynamic msg, List blob) { try { if (msg.mergeFromBuffer(blob) != blob.length) { @@ -63,21 +60,22 @@ dynamic protoStrictParse(dynamic msg, List blob) { return msg; } +List checkPlayerCommit(pb.PlayerCommit msg, + int minExcessFee, int maxExcessFee, int numComponents) { + check(msg.initialCommitments.length == numComponents, + "wrong number of component commitments"); + check(msg.blindSigRequests.length == numComponents, + "wrong number of blind sig requests"); -List checkPlayerCommit( - pb.PlayerCommit msg, - int minExcessFee, - int maxExcessFee, - int numComponents - ) { - check(msg.initialCommitments.length == numComponents, "wrong number of component commitments"); - check(msg.blindSigRequests.length == numComponents, "wrong number of blind sig requests"); - - check(minExcessFee <= msg.excessFee.toInt() && msg.excessFee.toInt() <= maxExcessFee, "bad excess fee"); + check( + minExcessFee <= msg.excessFee.toInt() && + msg.excessFee.toInt() <= maxExcessFee, + "bad excess fee"); check(msg.randomNumberCommitment.length == 32, "bad random commit"); check(msg.pedersenTotalNonce.length == 32, "bad nonce"); - check(msg.blindSigRequests.every((r) => r.length == 32), "bad blind sig request"); + check(msg.blindSigRequests.every((r) => r.length == 32), + "bad blind sig request"); List commitMessages = []; for (var cblob in msg.initialCommitments) { @@ -85,11 +83,15 @@ List checkPlayerCommit( check(cmsg.saltedComponentHash.length == 32, "bad salted hash"); var P = cmsg.amountCommitment; check(P.length == 65 && P[0] == 4, "bad commitment point"); - check(cmsg.communicationKey.length == 33 && (cmsg.communicationKey[0] == 2 || cmsg.communicationKey[0] == 3), "bad communication key"); + check( + cmsg.communicationKey.length == 33 && + (cmsg.communicationKey[0] == 2 || cmsg.communicationKey[0] == 3), + "bad communication key"); commitMessages.add(cmsg); } - Uint8List HBytes = Uint8List.fromList([0x02] + 'CashFusion gives us fungibility.'.codeUnits); + Uint8List HBytes = + Uint8List.fromList([0x02] + 'CashFusion gives us fungibility.'.codeUnits); ECDomainParameters params = ECDomainParameters('secp256k1'); ECPoint? HMaybe = params.curve.decodePoint(HBytes); if (HMaybe == null) { @@ -102,26 +104,28 @@ List checkPlayerCommit( var pointsum; // Verify pedersen commitment try { - pointsum = Commitment.add_points(commitMessages.map((m) => Uint8List.fromList(m.amountCommitment)).toList()); - claimedCommit = setup.commit(BigInt.from(msg.excessFee.toInt()), nonce: Util.bytesToBigInt(Uint8List.fromList(msg.pedersenTotalNonce))); + pointsum = Commitment.add_points(commitMessages + .map((m) => Uint8List.fromList(m.amountCommitment)) + .toList()); + claimedCommit = setup.commit(BigInt.from(msg.excessFee.toInt()), + nonce: Util.bytesToBigInt(Uint8List.fromList(msg.pedersenTotalNonce))); - check(pointsum == claimedCommit.PUncompressed, "pedersen commitment mismatch"); + check(pointsum == claimedCommit.PUncompressed, + "pedersen commitment mismatch"); } catch (e) { throw ValidationError("pedersen commitment verification error"); } - check(pointsum == claimedCommit.PUncompressed, "pedersen commitment mismatch"); + check( + pointsum == claimedCommit.PUncompressed, "pedersen commitment mismatch"); return commitMessages; } - Tuple checkCovertComponent( pb.CovertComponent msg, ECPoint roundPubkey, int componentFeerate) { var messageHash = Util.sha256(Uint8List.fromList(msg.component)); check(msg.signature.length == 64, "bad message signature"); - check( - Util.schnorrVerify( - roundPubkey, msg.signature, messageHash), + check(Util.schnorrVerify(roundPubkey, msg.signature, messageHash), "bad message signature"); var cmsg = protoStrictParse(pb.Component(), msg.component); @@ -133,7 +137,8 @@ Tuple checkCovertComponent( var inp = cmsg.input; check(inp.prevTxid.length == 32, "bad txid"); check( - (inp.pubkey.length == 33 && (inp.pubkey[0] == 2 || inp.pubkey[0] == 3)) || + (inp.pubkey.length == 33 && + (inp.pubkey[0] == 2 || inp.pubkey[0] == 3)) || (inp.pubkey.length == 65 && inp.pubkey[0] == 4), "bad pubkey"); sortKey = 'i' + @@ -146,8 +151,7 @@ Tuple checkCovertComponent( // Basically just checks if its ok address. should throw error if not. addr = Util.getAddressFromOutputScript(out.scriptpubkey); - check( - out.amount >= Util.dustLimit(out.scriptpubkey.length), "dust output"); + check(out.amount >= Util.dustLimit(out.scriptpubkey.length), "dust output"); sortKey = 'o' + out.amount.toString() + String.fromCharCodes(out.scriptpubkey) + @@ -162,14 +166,14 @@ Tuple checkCovertComponent( } pb.InputComponent? validateProofInternal( - Uint8List proofBlob, - pb.InitialCommitment commitment, - List allComponents, - List badComponents, - int componentFeerate, - ) { - - Uint8List HBytes = Uint8List.fromList([0x02] + 'CashFusion gives us fungibility.'.codeUnits); + Uint8List proofBlob, + pb.InitialCommitment commitment, + List allComponents, + List badComponents, + int componentFeerate, +) { + Uint8List HBytes = + Uint8List.fromList([0x02] + 'CashFusion gives us fungibility.'.codeUnits); ECDomainParameters params = ECDomainParameters('secp256k1'); ECPoint? HMaybe = params.curve.decodePoint(HBytes); if (HMaybe == null) { @@ -218,7 +222,7 @@ pb.InputComponent? validateProofInternal( "pedersen commitment mismatch", ); - if(comp.hasInput()){ + if (comp.hasInput()) { return comp.input; } else { return null; @@ -226,14 +230,14 @@ pb.InputComponent? validateProofInternal( } Future validateBlame( - pb.Blames_BlameProof blame, - Uint8List encProof, - Uint8List srcCommitBlob, - Uint8List destCommitBlob, - List allComponents, - List badComponents, - int componentFeerate, - ) async { + pb.Blames_BlameProof blame, + Uint8List encProof, + Uint8List srcCommitBlob, + Uint8List destCommitBlob, + List allComponents, + List badComponents, + int componentFeerate, +) async { var destCommit = pb.InitialCommitment(); destCommit.mergeFromBuffer(destCommitBlob); var destPubkey = destCommit.communicationKey; @@ -246,8 +250,10 @@ Future validateBlame( if (decrypter == pb.Blames_BlameProof_Decrypter.privkey) { var privkey = Uint8List.fromList(blame.privkey); check(privkey.length == 32, 'bad blame privkey'); - var privkeyHexStr = Util.bytesToHex(privkey); // Convert bytes to hex string. - var privkeyBigInt = BigInt.parse(privkeyHexStr, radix: 16); // Convert hex string to BigInt. + var privkeyHexStr = + Util.bytesToHex(privkey); // Convert bytes to hex string. + var privkeyBigInt = + BigInt.parse(privkeyHexStr, radix: 16); // Convert hex string to BigInt. var privateKey = ECPrivateKey(privkeyBigInt, params); // Create ECPrivateKey var pubkeys = Util.pubkeysFromPrivkey(privkeyHexStr); check(destCommit.communicationKey == pubkeys[1], 'bad blame privkey'); @@ -282,11 +288,13 @@ Future validateBlame( } if (!blame.needLookupBlockchain) { - throw ValidationError('blame indicated internal inconsistency, none found!'); + throw ValidationError( + 'blame indicated internal inconsistency, none found!'); } if (inpComp == null) { - throw ValidationError('blame indicated blockchain error on a non-input component'); + throw ValidationError( + 'blame indicated blockchain error on a non-input component'); } return inpComp;