mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-26 05:19:22 +00:00
Merge branch 'fusiondart' into origin/fusion
This commit is contained in:
commit
cb9fa5a5b0
21 changed files with 18 additions and 9323 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -7,3 +7,6 @@
|
||||||
[submodule "crypto_plugins/flutter_liblelantus"]
|
[submodule "crypto_plugins/flutter_liblelantus"]
|
||||||
path = crypto_plugins/flutter_liblelantus
|
path = crypto_plugins/flutter_liblelantus
|
||||||
url = https://github.com/cypherstack/flutter_liblelantus.git
|
url = https://github.com/cypherstack/flutter_liblelantus.git
|
||||||
|
[submodule "fusiondart"]
|
||||||
|
path = fusiondart
|
||||||
|
url = https://github.com/cypherstack/fusiondart
|
||||||
|
|
1
fusiondart
Submodule
1
fusiondart
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit d7e42820f0511636c47dd91e9c4f425f5f9d5be3
|
|
@ -1,178 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.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();
|
|
||||||
|
|
||||||
Map<Type, PbCreateFunc> pbClassCreators = {
|
|
||||||
CovertResponse: () => CovertResponse(),
|
|
||||||
ClientMessage: () => ClientMessage(),
|
|
||||||
InputComponent: () => InputComponent(),
|
|
||||||
OutputComponent: () => OutputComponent(),
|
|
||||||
BlankComponent: () => BlankComponent(),
|
|
||||||
Component: () => Component(),
|
|
||||||
InitialCommitment: () => InitialCommitment(),
|
|
||||||
Proof: () => Proof(),
|
|
||||||
ClientHello: () => ClientHello(),
|
|
||||||
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(),
|
|
||||||
ServerMessage: () => ServerMessage(),
|
|
||||||
CovertMessage: () => CovertMessage(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Future<void> sendPb(
|
|
||||||
Connection connection, Type pbClass, GeneratedMessage subMsg,
|
|
||||||
{Duration? timeout}) async {
|
|
||||||
// Construct the outer message with the submessage.
|
|
||||||
|
|
||||||
if (pbClassCreators[pbClass] == null) {
|
|
||||||
print('pbClassCreators[pbClass] is null');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pbMessage = pbClassCreators[pbClass]!()..mergeFromMessage(subMsg);
|
|
||||||
final msgBytes = pbMessage.writeToBuffer();
|
|
||||||
try {
|
|
||||||
await connection.sendMessage(msgBytes, timeout: timeout);
|
|
||||||
} on SocketException {
|
|
||||||
throw FusionError('Connection closed by remote');
|
|
||||||
} on TimeoutException {
|
|
||||||
throw FusionError('Timed out during send');
|
|
||||||
} catch (e) {
|
|
||||||
throw FusionError('Communications error: ${e.runtimeType}: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendPb2(SocketWrapper socketwrapper, Connection connection,
|
|
||||||
Type pbClass, GeneratedMessage subMsg,
|
|
||||||
{Duration? timeout}) async {
|
|
||||||
// Construct the outer message with the submessage.
|
|
||||||
|
|
||||||
if (pbClassCreators[pbClass] == null) {
|
|
||||||
print('pbClassCreators[pbClass] is null');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pbMessage = pbClassCreators[pbClass]!()..mergeFromMessage(subMsg);
|
|
||||||
final msgBytes = pbMessage.writeToBuffer();
|
|
||||||
try {
|
|
||||||
await connection.sendMessageWithSocketWrapper(socketwrapper, msgBytes,
|
|
||||||
timeout: timeout);
|
|
||||||
} on SocketException {
|
|
||||||
throw FusionError('Connection closed by remote');
|
|
||||||
} on TimeoutException {
|
|
||||||
throw FusionError('Timed out during send');
|
|
||||||
} catch (e) {
|
|
||||||
throw FusionError('Communications error: ${e.runtimeType}: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Tuple<GeneratedMessage, String>> recvPb2(SocketWrapper socketwrapper,
|
|
||||||
Connection connection, Type pbClass, List<String> expectedFieldNames,
|
|
||||||
{Duration? timeout}) async {
|
|
||||||
try {
|
|
||||||
List<int> blob =
|
|
||||||
await connection.recv_message2(socketwrapper, timeout: timeout);
|
|
||||||
|
|
||||||
var pbMessage = pbClassCreators[pbClass]!()..mergeFromBuffer(blob);
|
|
||||||
|
|
||||||
if (!pbMessage.isInitialized()) {
|
|
||||||
throw FusionError('Incomplete message received');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var name in expectedFieldNames) {
|
|
||||||
var fieldInfo = pbMessage.info_.byName[name];
|
|
||||||
|
|
||||||
if (fieldInfo == null) {
|
|
||||||
throw FusionError('Expected field not found in message: $name');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pbMessage.hasField(fieldInfo.tagNumber)) {
|
|
||||||
return Tuple(pbMessage, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw FusionError(
|
|
||||||
'None of the expected fields found in the received message');
|
|
||||||
} catch (e) {
|
|
||||||
// Handle different exceptions here
|
|
||||||
if (e is SocketException) {
|
|
||||||
throw FusionError('Connection closed by remote');
|
|
||||||
} else if (e is InvalidProtocolBufferException) {
|
|
||||||
throw FusionError('Message decoding error: ' + e.toString());
|
|
||||||
} else if (e is TimeoutException) {
|
|
||||||
throw FusionError('Timed out during receive');
|
|
||||||
} else if (e is OSError && e.errorCode == 9) {
|
|
||||||
throw FusionError('Connection closed by local');
|
|
||||||
} else {
|
|
||||||
throw FusionError(
|
|
||||||
'Communications error: ${e.runtimeType}: ${e.toString()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Tuple<GeneratedMessage, String>> recvPb(
|
|
||||||
Connection connection, Type pbClass, List<String> expectedFieldNames,
|
|
||||||
{Duration? timeout}) async {
|
|
||||||
try {
|
|
||||||
List<int> blob = await connection.recv_message(timeout: timeout);
|
|
||||||
|
|
||||||
var pbMessage = pbClassCreators[pbClass]!()..mergeFromBuffer(blob);
|
|
||||||
|
|
||||||
if (!pbMessage.isInitialized()) {
|
|
||||||
throw FusionError('Incomplete message received');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var name in expectedFieldNames) {
|
|
||||||
var fieldInfo = pbMessage.info_.byName[name];
|
|
||||||
|
|
||||||
if (fieldInfo == null) {
|
|
||||||
throw FusionError('Expected field not found in message: $name');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pbMessage.hasField(fieldInfo.tagNumber)) {
|
|
||||||
return Tuple(pbMessage, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw FusionError(
|
|
||||||
'None of the expected fields found in the received message');
|
|
||||||
} catch (e) {
|
|
||||||
// Handle different exceptions here
|
|
||||||
if (e is SocketException) {
|
|
||||||
throw FusionError('Connection closed by remote');
|
|
||||||
} else if (e is InvalidProtocolBufferException) {
|
|
||||||
throw FusionError('Message decoding error: ' + e.toString());
|
|
||||||
} else if (e is TimeoutException) {
|
|
||||||
throw FusionError('Timed out during receive');
|
|
||||||
} else if (e is OSError && e.errorCode == 9) {
|
|
||||||
throw FusionError('Connection closed by local');
|
|
||||||
} else {
|
|
||||||
throw FusionError(
|
|
||||||
'Communications error: ${e.runtimeType}: ${e.toString()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,290 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
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
|
|
||||||
remove data from a buffer but its a local copy , might not actually
|
|
||||||
remove the data from the socket buffer. We may need a wrapper class for the buffer??
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
class BadFrameError extends Error {
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
BadFrameError(this.message);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => message;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Connection> 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);
|
|
||||||
if (ssl) {
|
|
||||||
// We can use SecureSocket.secure to upgrade socket connection to SSL/TLS.
|
|
||||||
socket = await SecureSocket.secure(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
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]);
|
|
||||||
final Uint8List recvbuf = Uint8List(0);
|
|
||||||
|
|
||||||
Connection({required this.socket, this.timeout = const Duration(seconds: 1)});
|
|
||||||
|
|
||||||
Connection.withoutSocket({this.timeout = const Duration(seconds: 1)});
|
|
||||||
|
|
||||||
Future<void> sendMessageWithSocketWrapper(
|
|
||||||
SocketWrapper socketwrapper, List<int> msg,
|
|
||||||
{Duration? timeout}) async {
|
|
||||||
timeout ??= this.timeout;
|
|
||||||
print("DEBUG sendmessage msg sending ");
|
|
||||||
print(msg);
|
|
||||||
final lengthBytes = Uint8List(4);
|
|
||||||
final byteData = ByteData.view(lengthBytes.buffer);
|
|
||||||
byteData.setUint32(0, msg.length, Endian.big);
|
|
||||||
|
|
||||||
final frame = <int>[]
|
|
||||||
..addAll(Connection.magic)
|
|
||||||
..addAll(lengthBytes)
|
|
||||||
..addAll(msg);
|
|
||||||
|
|
||||||
try {
|
|
||||||
socketwrapper.send(frame);
|
|
||||||
} on SocketException catch (e) {
|
|
||||||
throw TimeoutException('Socket write timed out', timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendMessage(List<int> 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);
|
|
||||||
final frame = <int>[]
|
|
||||||
..addAll(Connection.magic)
|
|
||||||
..addAll(lengthBytes)
|
|
||||||
..addAll(msg);
|
|
||||||
|
|
||||||
try {
|
|
||||||
StreamController<List<int>> controller = StreamController();
|
|
||||||
|
|
||||||
controller.stream.listen((data) {
|
|
||||||
socket?.add(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
controller.add(frame);
|
|
||||||
// Remove the socket.flush() if it doesn't help.
|
|
||||||
// await socket?.flush();
|
|
||||||
} catch (e) {
|
|
||||||
print('Error when adding to controller: $e');
|
|
||||||
} finally {
|
|
||||||
controller.close();
|
|
||||||
}
|
|
||||||
} on SocketException catch (e) {
|
|
||||||
throw TimeoutException('Socket write timed out', timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
socket?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> fillBuf2(
|
|
||||||
SocketWrapper socketwrapper, List<int> recvBuf, int n,
|
|
||||||
{Duration? timeout}) async {
|
|
||||||
final maxTime = timeout != null ? DateTime.now().add(timeout) : null;
|
|
||||||
|
|
||||||
await for (var data in socketwrapper.socket!.cast<List<int>>()) {
|
|
||||||
print("DEBUG fillBuf2 1 - new data received: $data");
|
|
||||||
if (maxTime != null && DateTime.now().isAfter(maxTime)) {
|
|
||||||
throw SocketException('Timeout');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.isEmpty) {
|
|
||||||
if (recvBuf.isNotEmpty) {
|
|
||||||
throw SocketException('Connection ended mid-message.');
|
|
||||||
} else {
|
|
||||||
throw SocketException('Connection ended while awaiting message.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recvBuf.addAll(data);
|
|
||||||
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");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return recvBuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> fillBuf(int n, {Duration? timeout}) async {
|
|
||||||
var recvBuf = <int>[];
|
|
||||||
socket?.listen((data) {
|
|
||||||
print('Received from server: $data');
|
|
||||||
}, onDone: () {
|
|
||||||
print('Server closed connection.');
|
|
||||||
socket?.destroy();
|
|
||||||
}, onError: (error) {
|
|
||||||
print('Error: $error');
|
|
||||||
socket?.destroy();
|
|
||||||
});
|
|
||||||
return recvBuf;
|
|
||||||
|
|
||||||
StreamSubscription<List<int>>? subscription; // Declaration moved here
|
|
||||||
subscription = socket!.listen(
|
|
||||||
(List<int> data) {
|
|
||||||
recvBuf.addAll(data);
|
|
||||||
if (recvBuf.length >= n) {
|
|
||||||
subscription?.cancel();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: (e) {
|
|
||||||
subscription?.cancel();
|
|
||||||
if (e is Exception) {
|
|
||||||
throw e;
|
|
||||||
} else {
|
|
||||||
throw Exception(e ?? 'Error in `subscription` socket!.listen');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDone: () {
|
|
||||||
print("DEBUG ON DONE");
|
|
||||||
if (recvBuf.length < n) {
|
|
||||||
throw SocketException(
|
|
||||||
'Connection closed before enough data was received');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (timeout != null) {
|
|
||||||
Future.delayed(timeout, () {
|
|
||||||
if (recvBuf.length < n) {
|
|
||||||
subscription?.cancel();
|
|
||||||
throw SocketException('Timeout');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return recvBuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> recv_message2(SocketWrapper socketwrapper,
|
|
||||||
{Duration? timeout}) async {
|
|
||||||
print("START OF RECV2");
|
|
||||||
if (timeout == null) {
|
|
||||||
timeout = this.timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
final maxTime = timeout != null ? DateTime.now().add(timeout) : null;
|
|
||||||
|
|
||||||
List<int> recvBuf = [];
|
|
||||||
int bytesRead = 0;
|
|
||||||
|
|
||||||
print("DEBUG recv_message2 1 - about to read the header");
|
|
||||||
|
|
||||||
try {
|
|
||||||
await for (var data in socketwrapper.receiveStream) {
|
|
||||||
if (maxTime != null && DateTime.now().isAfter(maxTime)) {
|
|
||||||
throw SocketException('Timeout');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.isEmpty) {
|
|
||||||
if (recvBuf.isNotEmpty) {
|
|
||||||
throw SocketException('Connection ended mid-message.');
|
|
||||||
} else {
|
|
||||||
throw SocketException('Connection ended while awaiting message.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recvBuf.addAll(data);
|
|
||||||
|
|
||||||
if (bytesRead < 12) {
|
|
||||||
bytesRead += data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recvBuf.length >= 12) {
|
|
||||||
final magic = recvBuf.sublist(0, 8);
|
|
||||||
|
|
||||||
if (!ListEquality().equals(magic, Connection.magic)) {
|
|
||||||
throw BadFrameError('Bad magic in frame: ${hex.encode(magic)}');
|
|
||||||
}
|
|
||||||
|
|
||||||
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)');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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(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 5 - message content: $message");
|
|
||||||
print("END OF RECV2");
|
|
||||||
return message;
|
|
||||||
} else {
|
|
||||||
// Throwing exception if the length doesn't match
|
|
||||||
throw Exception(
|
|
||||||
'Message length mismatch: expected ${12 + messageLength} bytes, received ${recvBuf.length} bytes.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} on SocketException catch (e) {
|
|
||||||
print('Socket exception: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a default return in case of exceptions.
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> recv_message({Duration? timeout}) async {
|
|
||||||
// DEPRECATED
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} // END OF CLASS
|
|
|
@ -1,484 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:collection';
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> 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));
|
|
||||||
sock.write("GET\n");
|
|
||||||
List<int> data = await sock.first;
|
|
||||||
sock.destroy();
|
|
||||||
if (utf8.decode(data).contains("Tor is not an HTTP Proxy")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} on SocketException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TorLimiter {
|
|
||||||
Queue<DateTime> deque = Queue<DateTime>();
|
|
||||||
int lifetime;
|
|
||||||
// Declare a lock here, may need a special Dart package for this
|
|
||||||
int _count = 0;
|
|
||||||
|
|
||||||
TorLimiter(this.lifetime);
|
|
||||||
|
|
||||||
void cleanup() {}
|
|
||||||
|
|
||||||
int get count {
|
|
||||||
// return some default value for now
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bump() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
TorLimiter limiter = TorLimiter(TOR_COOLDOWN_TIME);
|
|
||||||
|
|
||||||
double randTrap(Random rng) {
|
|
||||||
final sixth = 1.0 / 6;
|
|
||||||
final f = rng.nextDouble();
|
|
||||||
final fc = 1.0 - f;
|
|
||||||
|
|
||||||
if (f < sixth) {
|
|
||||||
return sqrt(0.375 * f);
|
|
||||||
} else if (fc < sixth) {
|
|
||||||
return 1.0 - sqrt(0.375 * fc);
|
|
||||||
} else {
|
|
||||||
return 0.75 * f + 0.125;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CovertConnection {
|
|
||||||
Connection? connection; // replace dynamic with the type of your connection
|
|
||||||
int? slotNum;
|
|
||||||
DateTime? tPing;
|
|
||||||
int? connNumber;
|
|
||||||
Completer<bool> wakeup = Completer();
|
|
||||||
double? delay;
|
|
||||||
|
|
||||||
Future<bool> waitWakeupOrTime(DateTime? t) async {
|
|
||||||
if (t == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var remTime = t.difference(DateTime.now()).inMilliseconds;
|
|
||||||
remTime = remTime > 0 ? remTime : 0;
|
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: remTime));
|
|
||||||
wakeup.complete(true);
|
|
||||||
|
|
||||||
var wasSet = await wakeup.future;
|
|
||||||
wakeup = Completer();
|
|
||||||
return wasSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ping() {
|
|
||||||
if (this.connection != null) {
|
|
||||||
sendPb(this.connection!, CovertMessage, Ping(),
|
|
||||||
timeout: Duration(seconds: 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tPing = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void inactive() {
|
|
||||||
throw Unrecoverable("Timed out from inactivity (this is a bug!)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
CovertSlot(this.submitTimeout) : done = true;
|
|
||||||
DateTime? t_submit;
|
|
||||||
|
|
||||||
// Define a getter for tSubmit
|
|
||||||
DateTime? get tSubmit => t_submit;
|
|
||||||
|
|
||||||
Future<void> submit() async {
|
|
||||||
var connection = covConn?.connection;
|
|
||||||
|
|
||||||
if (connection == null) {
|
|
||||||
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));
|
|
||||||
|
|
||||||
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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PrintError {
|
|
||||||
// Declare properties here
|
|
||||||
}
|
|
||||||
|
|
||||||
class CovertSubmitter extends PrintError {
|
|
||||||
// Declare properties here
|
|
||||||
List<CovertSlot> slots;
|
|
||||||
bool done = true;
|
|
||||||
String failure_exception = "";
|
|
||||||
int num_slots;
|
|
||||||
|
|
||||||
bool stopping = false;
|
|
||||||
Map<String, dynamic>? proxyOpts;
|
|
||||||
String? randtag;
|
|
||||||
String? destAddr;
|
|
||||||
int? destPort;
|
|
||||||
bool ssl = false;
|
|
||||||
Object lock = Object();
|
|
||||||
int countFailed = 0;
|
|
||||||
int countEstablished = 0;
|
|
||||||
int countAttempted = 0;
|
|
||||||
Random rng = Random.secure();
|
|
||||||
int? randSpan;
|
|
||||||
DateTime? stopTStart;
|
|
||||||
List<CovertConnection> spareConnections = [];
|
|
||||||
String? failureException;
|
|
||||||
int submit_timeout = 0;
|
|
||||||
|
|
||||||
CovertSubmitter(
|
|
||||||
String dest_addr,
|
|
||||||
int dest_port,
|
|
||||||
bool ssl,
|
|
||||||
String tor_host,
|
|
||||||
int tor_port,
|
|
||||||
this.num_slots,
|
|
||||||
double randSpan, // changed from int to double
|
|
||||||
double submit_timeout) // changed from int to double
|
|
||||||
: slots = List<CovertSlot>.generate(
|
|
||||||
num_slots, (index) => CovertSlot(submit_timeout.toInt())) {
|
|
||||||
// constructor body...
|
|
||||||
}
|
|
||||||
|
|
||||||
void wakeAll() {
|
|
||||||
for (var s in slots) {
|
|
||||||
if (s.covConn != null) {
|
|
||||||
s.covConn!.wakeup.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var c in spareConnections) {
|
|
||||||
c.wakeup.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStopTime(int tstart) {
|
|
||||||
this.stopTStart = DateTime.fromMillisecondsSinceEpoch(tstart * 1000);
|
|
||||||
if (this.stopping) {
|
|
||||||
this.wakeAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop([Exception? exception]) {
|
|
||||||
if (this.stopping) {
|
|
||||||
// already requested!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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");
|
|
||||||
this.wakeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
// PYTHON USES MULTITHREADING, WHICH ISNT IMPLEMENTED HERE YET
|
|
||||||
void scheduleConnections(DateTime tStart, Duration tSpan,
|
|
||||||
{int numSpares = 0, int connectTimeout = 10}) {
|
|
||||||
var newConns = <CovertConnection>[];
|
|
||||||
|
|
||||||
for (var sNum = 0; sNum < this.slots.length; sNum++) {
|
|
||||||
var s = this.slots[sNum];
|
|
||||||
if (s.covConn == null) {
|
|
||||||
s.covConn = CovertConnection();
|
|
||||||
s.covConn?.slotNum = sNum;
|
|
||||||
CovertConnection? myCovConn = s.covConn;
|
|
||||||
if (myCovConn != null) {
|
|
||||||
newConns.add(myCovConn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var numNewSpares = max(0, numSpares - this.spareConnections.length);
|
|
||||||
var newSpares = List.generate(numNewSpares, (index) => CovertConnection());
|
|
||||||
this.spareConnections = [...newSpares, ...this.spareConnections];
|
|
||||||
|
|
||||||
newConns.addAll(newSpares);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void scheduleSubmit(
|
|
||||||
int slotNum, DateTime tStart, pb.GeneratedMessage subMsg) {
|
|
||||||
var slot = slots[slotNum];
|
|
||||||
|
|
||||||
assert(slot.done, "tried to set new work when prior work not done");
|
|
||||||
|
|
||||||
slot.subMsg = subMsg;
|
|
||||||
slot.done = false;
|
|
||||||
slot.t_submit = tStart;
|
|
||||||
var covConn = slot.covConn;
|
|
||||||
if (covConn != null) {
|
|
||||||
covConn.wakeup.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void scheduleSubmissions(DateTime tStart, List<dynamic> slotMessages) {
|
|
||||||
// Convert to list (Dart does not have tuples)
|
|
||||||
slotMessages = List.from(slotMessages);
|
|
||||||
|
|
||||||
// Ensure that the number of slot messages equals the number of slots
|
|
||||||
assert(slotMessages.length == slots.length);
|
|
||||||
|
|
||||||
// First, notify the spare connections that they will need to make a ping.
|
|
||||||
// Note that Dart does not require making a copy of the list before iteration,
|
|
||||||
// since Dart does not support mutation during iteration.
|
|
||||||
for (var c in spareConnections) {
|
|
||||||
c.tPing = tStart;
|
|
||||||
c.wakeup.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, notify the slots that there is a message to submit.
|
|
||||||
for (var i = 0; i < slots.length; i++) {
|
|
||||||
var slot = slots[i];
|
|
||||||
var subMsg = slotMessages[i] as pb.GeneratedMessage;
|
|
||||||
var covConn = slot.covConn;
|
|
||||||
|
|
||||||
if (covConn != null) {
|
|
||||||
if (subMsg == null) {
|
|
||||||
covConn.tPing = tStart;
|
|
||||||
} else {
|
|
||||||
slot.subMsg = subMsg;
|
|
||||||
slot.done = false;
|
|
||||||
slot.t_submit = tStart;
|
|
||||||
}
|
|
||||||
covConn.wakeup.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future runConnection(CovertConnection covConn, int connTime, double randDelay,
|
|
||||||
int connectTimeout) async {
|
|
||||||
// Main loop for connection thread
|
|
||||||
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) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final tBegin = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// STATE 1 - connecting
|
|
||||||
Map<String, dynamic> proxyOpts;
|
|
||||||
|
|
||||||
if (this.proxyOpts == null) {
|
|
||||||
proxyOpts = {};
|
|
||||||
} else {
|
|
||||||
final unique = 'CF${this.randtag}_${covConn.connNumber}';
|
|
||||||
proxyOpts = {
|
|
||||||
'proxy_username': unique,
|
|
||||||
'proxy_password': unique,
|
|
||||||
};
|
|
||||||
proxyOpts.addAll(this.proxyOpts!);
|
|
||||||
}
|
|
||||||
|
|
||||||
limiter.bump();
|
|
||||||
|
|
||||||
try {
|
|
||||||
final connection = await openConnection(
|
|
||||||
this.destAddr!, this.destPort!,
|
|
||||||
connTimeout: connectTimeout.toDouble(),
|
|
||||||
ssl: this.ssl,
|
|
||||||
socksOpts: proxyOpts);
|
|
||||||
covConn.connection = connection;
|
|
||||||
} catch (e) {
|
|
||||||
this.countFailed++;
|
|
||||||
|
|
||||||
final tEnd = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
print(
|
|
||||||
'could not establish connection (after ${((tEnd - tBegin) / 1000).toStringAsFixed(3)}s): $e');
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// STATE 2 - working
|
|
||||||
while (!this.stopping) {
|
|
||||||
DateTime? nextTime;
|
|
||||||
final slotNum = covConn.slotNum;
|
|
||||||
Function()? action; // callback to hold the action function
|
|
||||||
|
|
||||||
// Second preference: submit something
|
|
||||||
if (slotNum != null) {
|
|
||||||
CovertSlot slot = this.slots[slotNum];
|
|
||||||
nextTime = slot.tSubmit;
|
|
||||||
action = slot.submit;
|
|
||||||
}
|
|
||||||
// Third preference: send a ping
|
|
||||||
if (nextTime == null && covConn.tPing != null) {
|
|
||||||
nextTime = covConn.tPing;
|
|
||||||
action = covConn.ping;
|
|
||||||
}
|
|
||||||
// Last preference: wait doing nothing
|
|
||||||
if (nextTime == null) {
|
|
||||||
nextTime = DateTime.now()
|
|
||||||
.add(Duration(seconds: TIMEOUT_INACTIVE_CONNECTION));
|
|
||||||
action = covConn.inactive;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextTime = nextTime.add(Duration(seconds: randDelay.toInt()));
|
|
||||||
|
|
||||||
if (await covConn.waitWakeupOrTime(nextTime)) {
|
|
||||||
// got woken up ... let's go back and reevaluate what to do
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reached action time, time to do it
|
|
||||||
final label = "[${covConn.connNumber}-$slotNum]";
|
|
||||||
try {
|
|
||||||
await action?.call();
|
|
||||||
} catch (e) {
|
|
||||||
print("$label error $e");
|
|
||||||
rethrow;
|
|
||||||
} finally {
|
|
||||||
print("$label done");
|
|
||||||
}
|
|
||||||
|
|
||||||
lastActionTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// STATE 3 - stopping
|
|
||||||
while (true) {
|
|
||||||
final stopTime =
|
|
||||||
this.stopTStart?.add(Duration(seconds: randDelay.toInt())) ??
|
|
||||||
DateTime.now();
|
|
||||||
|
|
||||||
if (!(await covConn.waitWakeupOrTime(stopTime))) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("[${covConn.connNumber}] closing from stop");
|
|
||||||
} catch (e) {
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
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;
|
|
||||||
if (e != null) {
|
|
||||||
throw FusionError('Covert connections failed: ${e.runtimeType} $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkConnected() {
|
|
||||||
// Implement checkConnected logic here
|
|
||||||
this.checkOk();
|
|
||||||
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}).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkDone() {
|
|
||||||
// Implement checkDone logic here
|
|
||||||
this.checkOk();
|
|
||||||
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}).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
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<Uint8List> encrypt(Uint8List message, ECPoint pubkey,
|
|
||||||
{int? padToLength}) async {
|
|
||||||
ECPoint pubpoint;
|
|
||||||
try {
|
|
||||||
pubpoint = Util.ser_to_point(pubkey.getEncoded(true), params);
|
|
||||||
} catch (_) {
|
|
||||||
throw EncryptionFailed();
|
|
||||||
}
|
|
||||||
var nonceSec = Util.secureRandomBigInt(params.n.bitLength);
|
|
||||||
var G_times_nonceSec = params.G * nonceSec;
|
|
||||||
if (G_times_nonceSec == null) {
|
|
||||||
throw Exception('Multiplication of G with nonceSec resulted in null');
|
|
||||||
}
|
|
||||||
var noncePub = Util.point_to_ser(G_times_nonceSec, true);
|
|
||||||
|
|
||||||
var pubpoint_times_nonceSec = pubpoint * nonceSec;
|
|
||||||
if (pubpoint_times_nonceSec == 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 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
|
|
||||||
} 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);
|
|
||||||
|
|
||||||
final secretKey = SecretKey(key);
|
|
||||||
|
|
||||||
final macAlgorithm = Hmac(Sha256());
|
|
||||||
|
|
||||||
final cipher = AesCbc.with128bits(macAlgorithm: macAlgorithm);
|
|
||||||
|
|
||||||
final nonce = Uint8List(16); // Random 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)
|
|
||||||
..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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List> decryptWithSymmkey(Uint8List data, Uint8List key) async {
|
|
||||||
if (data.length < 33 + 16 + 16) {
|
|
||||||
throw DecryptionFailed();
|
|
||||||
}
|
|
||||||
var ciphertext = data.sublist(33, data.length - 16);
|
|
||||||
if (ciphertext.length % 16 != 0) {
|
|
||||||
throw DecryptionFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
final secretKey = SecretKey(key);
|
|
||||||
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 plaintext = await cipher.decrypt(secretBox, secretKey: secretKey);
|
|
||||||
|
|
||||||
if (plaintext.length < 4) {
|
|
||||||
throw DecryptionFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List uint8list = Uint8List.fromList(plaintext);
|
|
||||||
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<Tuple<Uint8List, Uint8List>> decrypt(
|
|
||||||
Uint8List data, ECPrivateKey privkey) async {
|
|
||||||
if (data.length < 33 + 16 + 16) {
|
|
||||||
throw DecryptionFailed();
|
|
||||||
}
|
|
||||||
var noncePub = data.sublist(0, 33);
|
|
||||||
ECPoint noncePoint;
|
|
||||||
try {
|
|
||||||
noncePoint = Util.ser_to_point(noncePub, params);
|
|
||||||
} catch (_) {
|
|
||||||
throw DecryptionFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// DOUBLE CHECK THIS IS RIGHT IDEA MATCHING PYTHON.
|
|
||||||
|
|
||||||
ECPoint G = params.G;
|
|
||||||
final List<int> key;
|
|
||||||
|
|
||||||
if (privkey.d != null) {
|
|
||||||
var point = (G * privkey.d)! + noncePoint;
|
|
||||||
key = crypto.sha256.convert(Util.point_to_ser(point!, true)).bytes;
|
|
||||||
// ...
|
|
||||||
var decryptedData = await decryptWithSymmkey(data, Uint8List.fromList(key));
|
|
||||||
return Tuple(decryptedData, Uint8List.fromList(key));
|
|
||||||
} else {
|
|
||||||
// Handle the situation where privkey.d or noncePoint is null
|
|
||||||
throw Exception("FIXME");
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,195 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:pointycastle/ecc/api.dart';
|
|
||||||
import 'package:stackwallet/services/cashfusion/util.dart';
|
|
||||||
|
|
||||||
ECDomainParameters getDefaultParams() {
|
|
||||||
return ECDomainParameters("secp256k1");
|
|
||||||
}
|
|
||||||
|
|
||||||
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"]);
|
|
||||||
String toString() => "NonceRangeError: $message";
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResultAtInfinity implements Exception {
|
|
||||||
final String message;
|
|
||||||
ResultAtInfinity([this.message = "Result is at infinity"]);
|
|
||||||
String toString() => "ResultAtInfinity: $message";
|
|
||||||
}
|
|
||||||
|
|
||||||
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"]);
|
|
||||||
String toString() => "InsecureHPoint: $message";
|
|
||||||
}
|
|
||||||
|
|
||||||
class PedersenSetup {
|
|
||||||
late ECPoint _H;
|
|
||||||
late ECPoint _HG;
|
|
||||||
late ECDomainParameters _params;
|
|
||||||
ECDomainParameters get params => _params;
|
|
||||||
|
|
||||||
PedersenSetup(this._H) {
|
|
||||||
_params = new ECDomainParameters("secp256k1");
|
|
||||||
// validate H point
|
|
||||||
if (!Util.isPointOnCurve(_H, _params.curve)) {
|
|
||||||
throw Exception('H is not a valid point on the curve');
|
|
||||||
}
|
|
||||||
_HG = Util.combinePubKeys([_H, _params.G]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List get H => _H.getEncoded(false);
|
|
||||||
Uint8List get HG => _HG.getEncoded(false);
|
|
||||||
|
|
||||||
Commitment commit(BigInt amount, {BigInt? nonce, Uint8List? PUncompressed}) {
|
|
||||||
return Commitment(this, amount, nonce: nonce, PUncompressed: PUncompressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Commitment {
|
|
||||||
late PedersenSetup setup; // Added setup property to Commitment class
|
|
||||||
late BigInt amountMod;
|
|
||||||
late BigInt nonce;
|
|
||||||
late 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;
|
|
||||||
|
|
||||||
if (this.nonce <= BigInt.zero || this.nonce >= setup.params.n) {
|
|
||||||
throw NonceRangeError();
|
|
||||||
}
|
|
||||||
|
|
||||||
ECPoint? Hpoint = setup._H;
|
|
||||||
ECPoint? HGpoint = setup._HG;
|
|
||||||
|
|
||||||
if (Hpoint == null || HGpoint == null) {
|
|
||||||
throw NullPointError();
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInt multiplier1 = (amountMod - this.nonce) % setup.params.n;
|
|
||||||
BigInt multiplier2 = this.nonce;
|
|
||||||
|
|
||||||
ECPoint? HpointMultiplied = Hpoint * multiplier1;
|
|
||||||
ECPoint? HGpointMultiplied = HGpoint * multiplier2;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void calcInitial(PedersenSetup setup, BigInt amount) {
|
|
||||||
amountMod = amount % setup.params.n;
|
|
||||||
nonce = Util.secureRandomBigInt(setup.params.n.bitLength);
|
|
||||||
|
|
||||||
ECPoint? Hpoint = setup._H;
|
|
||||||
ECPoint? HGpoint = setup._HG;
|
|
||||||
|
|
||||||
if (nonce <= BigInt.zero || nonce >= setup.params.n) {
|
|
||||||
throw NonceRangeError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hpoint == null || HGpoint == null) {
|
|
||||||
throw NullPointError();
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInt multiplier1 = amountMod;
|
|
||||||
BigInt multiplier2 = nonce;
|
|
||||||
|
|
||||||
ECPoint? HpointMultiplied = Hpoint * multiplier1;
|
|
||||||
ECPoint? HGpointMultiplied = HGpoint * multiplier2;
|
|
||||||
|
|
||||||
ECPoint? Ppoint = HpointMultiplied != null && HGpointMultiplied != null
|
|
||||||
? HpointMultiplied + HGpointMultiplied
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (Ppoint == setup.params.curve.infinity) {
|
|
||||||
throw ResultAtInfinity();
|
|
||||||
}
|
|
||||||
|
|
||||||
PUncompressed = Ppoint?.getEncoded(false) ?? Uint8List(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uint8List add_points(Iterable<Uint8List> pointsIterable) {
|
|
||||||
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
|
|
||||||
|
|
||||||
for (var i = 1; i < pointList.length; i++) {
|
|
||||||
pSum = (pSum + pointList[i])!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pSum == params.curve.infinity) {
|
|
||||||
throw Exception('Result is at infinity');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Util.point_to_ser(pSum, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Commitment addCommitments(Iterable<Commitment> commitmentIterable) {
|
|
||||||
BigInt ktotal = BigInt.zero; // Changed to BigInt from int
|
|
||||||
BigInt atotal = BigInt.zero; // Changed to BigInt from int
|
|
||||||
List<Uint8List> points = [];
|
|
||||||
List<PedersenSetup> setups = []; // Changed Setup to PedersenSetup
|
|
||||||
for (Commitment c in commitmentIterable) {
|
|
||||||
ktotal += c.nonce;
|
|
||||||
atotal += c.amountMod; // Changed from amount to amountMod
|
|
||||||
points.add(c.PUncompressed);
|
|
||||||
setups.add(c.setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (points.isEmpty) {
|
|
||||||
throw ArgumentError('Empty list');
|
|
||||||
}
|
|
||||||
|
|
||||||
PedersenSetup setup = setups[0]; // Changed Setup to PedersenSetup
|
|
||||||
if (!setups.every((s) => s == setup)) {
|
|
||||||
throw ArgumentError('Mismatched setups');
|
|
||||||
}
|
|
||||||
|
|
||||||
ktotal = ktotal % setup.params.n; // Changed order to setup.params.n
|
|
||||||
|
|
||||||
if (ktotal == BigInt.zero) {
|
|
||||||
// Changed comparison from 0 to BigInt.zero
|
|
||||||
throw Exception('Nonce range error');
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List? PUncompressed;
|
|
||||||
if (points.length < 512) {
|
|
||||||
try {
|
|
||||||
PUncompressed = add_points(points);
|
|
||||||
} on Exception {
|
|
||||||
PUncompressed = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PUncompressed = null;
|
|
||||||
}
|
|
||||||
return Commitment(setup, atotal,
|
|
||||||
nonce: ktotal, PUncompressed: PUncompressed);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# This script will build the dart files based on fusion.proto. Navigate to the protobuf directory where the fusion.proto is and run this, then copy the needed files to lib.
|
|
||||||
|
|
||||||
# The path to your .proto file. Adjust this if necessary.
|
|
||||||
PROTO_FILE="fusion.proto"
|
|
||||||
|
|
||||||
# Run the protoc command.
|
|
||||||
protoc --dart_out=grpc:. $PROTO_FILE
|
|
||||||
|
|
||||||
# After this, You should manually copy any needed dart files that were generated to the lib folder.
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +0,0 @@
|
||||||
///
|
|
||||||
// Generated code. Do not modify.
|
|
||||||
// source: fusion.proto
|
|
||||||
//
|
|
||||||
// @dart = 2.12
|
|
||||||
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
|
||||||
|
|
|
@ -1,427 +0,0 @@
|
||||||
///
|
|
||||||
// Generated code. Do not modify.
|
|
||||||
// source: fusion.proto
|
|
||||||
//
|
|
||||||
// @dart = 2.12
|
|
||||||
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
|
||||||
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
import 'dart:convert' as $convert;
|
|
||||||
import 'dart:typed_data' as $typed_data;
|
|
||||||
@$core.Deprecated('Use inputComponentDescriptor instead')
|
|
||||||
const InputComponent$json = const {
|
|
||||||
'1': 'InputComponent',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'prev_txid', '3': 1, '4': 2, '5': 12, '10': 'prevTxid'},
|
|
||||||
const {'1': 'prev_index', '3': 2, '4': 2, '5': 13, '10': 'prevIndex'},
|
|
||||||
const {'1': 'pubkey', '3': 3, '4': 2, '5': 12, '10': 'pubkey'},
|
|
||||||
const {'1': 'amount', '3': 4, '4': 2, '5': 4, '10': 'amount'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `InputComponent`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List inputComponentDescriptor = $convert.base64Decode('Cg5JbnB1dENvbXBvbmVudBIbCglwcmV2X3R4aWQYASACKAxSCHByZXZUeGlkEh0KCnByZXZfaW5kZXgYAiACKA1SCXByZXZJbmRleBIWCgZwdWJrZXkYAyACKAxSBnB1YmtleRIWCgZhbW91bnQYBCACKARSBmFtb3VudA==');
|
|
||||||
@$core.Deprecated('Use outputComponentDescriptor instead')
|
|
||||||
const OutputComponent$json = const {
|
|
||||||
'1': 'OutputComponent',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'scriptpubkey', '3': 1, '4': 2, '5': 12, '10': 'scriptpubkey'},
|
|
||||||
const {'1': 'amount', '3': 2, '4': 2, '5': 4, '10': 'amount'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `OutputComponent`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List outputComponentDescriptor = $convert.base64Decode('Cg9PdXRwdXRDb21wb25lbnQSIgoMc2NyaXB0cHVia2V5GAEgAigMUgxzY3JpcHRwdWJrZXkSFgoGYW1vdW50GAIgAigEUgZhbW91bnQ=');
|
|
||||||
@$core.Deprecated('Use blankComponentDescriptor instead')
|
|
||||||
const BlankComponent$json = const {
|
|
||||||
'1': 'BlankComponent',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `BlankComponent`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List blankComponentDescriptor = $convert.base64Decode('Cg5CbGFua0NvbXBvbmVudA==');
|
|
||||||
@$core.Deprecated('Use componentDescriptor instead')
|
|
||||||
const Component$json = const {
|
|
||||||
'1': 'Component',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'salt_commitment', '3': 1, '4': 2, '5': 12, '10': 'saltCommitment'},
|
|
||||||
const {'1': 'input', '3': 2, '4': 1, '5': 11, '6': '.fusion.InputComponent', '9': 0, '10': 'input'},
|
|
||||||
const {'1': 'output', '3': 3, '4': 1, '5': 11, '6': '.fusion.OutputComponent', '9': 0, '10': 'output'},
|
|
||||||
const {'1': 'blank', '3': 4, '4': 1, '5': 11, '6': '.fusion.BlankComponent', '9': 0, '10': 'blank'},
|
|
||||||
],
|
|
||||||
'8': const [
|
|
||||||
const {'1': 'component'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Component`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List componentDescriptor = $convert.base64Decode('CglDb21wb25lbnQSJwoPc2FsdF9jb21taXRtZW50GAEgAigMUg5zYWx0Q29tbWl0bWVudBIuCgVpbnB1dBgCIAEoCzIWLmZ1c2lvbi5JbnB1dENvbXBvbmVudEgAUgVpbnB1dBIxCgZvdXRwdXQYAyABKAsyFy5mdXNpb24uT3V0cHV0Q29tcG9uZW50SABSBm91dHB1dBIuCgVibGFuaxgEIAEoCzIWLmZ1c2lvbi5CbGFua0NvbXBvbmVudEgAUgVibGFua0ILCgljb21wb25lbnQ=');
|
|
||||||
@$core.Deprecated('Use initialCommitmentDescriptor instead')
|
|
||||||
const InitialCommitment$json = const {
|
|
||||||
'1': 'InitialCommitment',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'salted_component_hash', '3': 1, '4': 2, '5': 12, '10': 'saltedComponentHash'},
|
|
||||||
const {'1': 'amount_commitment', '3': 2, '4': 2, '5': 12, '10': 'amountCommitment'},
|
|
||||||
const {'1': 'communication_key', '3': 3, '4': 2, '5': 12, '10': 'communicationKey'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `InitialCommitment`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List initialCommitmentDescriptor = $convert.base64Decode('ChFJbml0aWFsQ29tbWl0bWVudBIyChVzYWx0ZWRfY29tcG9uZW50X2hhc2gYASACKAxSE3NhbHRlZENvbXBvbmVudEhhc2gSKwoRYW1vdW50X2NvbW1pdG1lbnQYAiACKAxSEGFtb3VudENvbW1pdG1lbnQSKwoRY29tbXVuaWNhdGlvbl9rZXkYAyACKAxSEGNvbW11bmljYXRpb25LZXk=');
|
|
||||||
@$core.Deprecated('Use proofDescriptor instead')
|
|
||||||
const Proof$json = const {
|
|
||||||
'1': 'Proof',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'component_idx', '3': 1, '4': 2, '5': 7, '10': 'componentIdx'},
|
|
||||||
const {'1': 'salt', '3': 2, '4': 2, '5': 12, '10': 'salt'},
|
|
||||||
const {'1': 'pedersen_nonce', '3': 3, '4': 2, '5': 12, '10': 'pedersenNonce'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Proof`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List proofDescriptor = $convert.base64Decode('CgVQcm9vZhIjCg1jb21wb25lbnRfaWR4GAEgAigHUgxjb21wb25lbnRJZHgSEgoEc2FsdBgCIAIoDFIEc2FsdBIlCg5wZWRlcnNlbl9ub25jZRgDIAIoDFINcGVkZXJzZW5Ob25jZQ==');
|
|
||||||
@$core.Deprecated('Use clientHelloDescriptor instead')
|
|
||||||
const ClientHello$json = const {
|
|
||||||
'1': 'ClientHello',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'version', '3': 1, '4': 2, '5': 12, '10': 'version'},
|
|
||||||
const {'1': 'genesis_hash', '3': 2, '4': 1, '5': 12, '10': 'genesisHash'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `ClientHello`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List clientHelloDescriptor = $convert.base64Decode('CgtDbGllbnRIZWxsbxIYCgd2ZXJzaW9uGAEgAigMUgd2ZXJzaW9uEiEKDGdlbmVzaXNfaGFzaBgCIAEoDFILZ2VuZXNpc0hhc2g=');
|
|
||||||
@$core.Deprecated('Use serverHelloDescriptor instead')
|
|
||||||
const ServerHello$json = const {
|
|
||||||
'1': 'ServerHello',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'tiers', '3': 1, '4': 3, '5': 4, '10': 'tiers'},
|
|
||||||
const {'1': 'num_components', '3': 2, '4': 2, '5': 13, '10': 'numComponents'},
|
|
||||||
const {'1': 'component_feerate', '3': 4, '4': 2, '5': 4, '10': 'componentFeerate'},
|
|
||||||
const {'1': 'min_excess_fee', '3': 5, '4': 2, '5': 4, '10': 'minExcessFee'},
|
|
||||||
const {'1': 'max_excess_fee', '3': 6, '4': 2, '5': 4, '10': 'maxExcessFee'},
|
|
||||||
const {'1': 'donation_address', '3': 15, '4': 1, '5': 9, '10': 'donationAddress'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `ServerHello`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List serverHelloDescriptor = $convert.base64Decode('CgtTZXJ2ZXJIZWxsbxIUCgV0aWVycxgBIAMoBFIFdGllcnMSJQoObnVtX2NvbXBvbmVudHMYAiACKA1SDW51bUNvbXBvbmVudHMSKwoRY29tcG9uZW50X2ZlZXJhdGUYBCACKARSEGNvbXBvbmVudEZlZXJhdGUSJAoObWluX2V4Y2Vzc19mZWUYBSACKARSDG1pbkV4Y2Vzc0ZlZRIkCg5tYXhfZXhjZXNzX2ZlZRgGIAIoBFIMbWF4RXhjZXNzRmVlEikKEGRvbmF0aW9uX2FkZHJlc3MYDyABKAlSD2RvbmF0aW9uQWRkcmVzcw==');
|
|
||||||
@$core.Deprecated('Use joinPoolsDescriptor instead')
|
|
||||||
const JoinPools$json = const {
|
|
||||||
'1': 'JoinPools',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'tiers', '3': 1, '4': 3, '5': 4, '10': 'tiers'},
|
|
||||||
const {'1': 'tags', '3': 2, '4': 3, '5': 11, '6': '.fusion.JoinPools.PoolTag', '10': 'tags'},
|
|
||||||
],
|
|
||||||
'3': const [JoinPools_PoolTag$json],
|
|
||||||
};
|
|
||||||
|
|
||||||
@$core.Deprecated('Use joinPoolsDescriptor instead')
|
|
||||||
const JoinPools_PoolTag$json = const {
|
|
||||||
'1': 'PoolTag',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'id', '3': 1, '4': 2, '5': 12, '10': 'id'},
|
|
||||||
const {'1': 'limit', '3': 2, '4': 2, '5': 13, '10': 'limit'},
|
|
||||||
const {'1': 'no_ip', '3': 3, '4': 1, '5': 8, '10': 'noIp'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `JoinPools`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List joinPoolsDescriptor = $convert.base64Decode('CglKb2luUG9vbHMSFAoFdGllcnMYASADKARSBXRpZXJzEi0KBHRhZ3MYAiADKAsyGS5mdXNpb24uSm9pblBvb2xzLlBvb2xUYWdSBHRhZ3MaRAoHUG9vbFRhZxIOCgJpZBgBIAIoDFICaWQSFAoFbGltaXQYAiACKA1SBWxpbWl0EhMKBW5vX2lwGAMgASgIUgRub0lw');
|
|
||||||
@$core.Deprecated('Use tierStatusUpdateDescriptor instead')
|
|
||||||
const TierStatusUpdate$json = const {
|
|
||||||
'1': 'TierStatusUpdate',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'statuses', '3': 1, '4': 3, '5': 11, '6': '.fusion.TierStatusUpdate.StatusesEntry', '10': 'statuses'},
|
|
||||||
],
|
|
||||||
'3': const [TierStatusUpdate_TierStatus$json, TierStatusUpdate_StatusesEntry$json],
|
|
||||||
};
|
|
||||||
|
|
||||||
@$core.Deprecated('Use tierStatusUpdateDescriptor instead')
|
|
||||||
const TierStatusUpdate_TierStatus$json = const {
|
|
||||||
'1': 'TierStatus',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'players', '3': 1, '4': 1, '5': 13, '10': 'players'},
|
|
||||||
const {'1': 'min_players', '3': 2, '4': 1, '5': 13, '10': 'minPlayers'},
|
|
||||||
const {'1': 'max_players', '3': 3, '4': 1, '5': 13, '10': 'maxPlayers'},
|
|
||||||
const {'1': 'time_remaining', '3': 4, '4': 1, '5': 13, '10': 'timeRemaining'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
@$core.Deprecated('Use tierStatusUpdateDescriptor instead')
|
|
||||||
const TierStatusUpdate_StatusesEntry$json = const {
|
|
||||||
'1': 'StatusesEntry',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'key', '3': 1, '4': 1, '5': 4, '10': 'key'},
|
|
||||||
const {'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.fusion.TierStatusUpdate.TierStatus', '10': 'value'},
|
|
||||||
],
|
|
||||||
'7': const {'7': true},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `TierStatusUpdate`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List tierStatusUpdateDescriptor = $convert.base64Decode('ChBUaWVyU3RhdHVzVXBkYXRlEkIKCHN0YXR1c2VzGAEgAygLMiYuZnVzaW9uLlRpZXJTdGF0dXNVcGRhdGUuU3RhdHVzZXNFbnRyeVIIc3RhdHVzZXMajwEKClRpZXJTdGF0dXMSGAoHcGxheWVycxgBIAEoDVIHcGxheWVycxIfCgttaW5fcGxheWVycxgCIAEoDVIKbWluUGxheWVycxIfCgttYXhfcGxheWVycxgDIAEoDVIKbWF4UGxheWVycxIlCg50aW1lX3JlbWFpbmluZxgEIAEoDVINdGltZVJlbWFpbmluZxpgCg1TdGF0dXNlc0VudHJ5EhAKA2tleRgBIAEoBFIDa2V5EjkKBXZhbHVlGAIgASgLMiMuZnVzaW9uLlRpZXJTdGF0dXNVcGRhdGUuVGllclN0YXR1c1IFdmFsdWU6AjgB');
|
|
||||||
@$core.Deprecated('Use fusionBeginDescriptor instead')
|
|
||||||
const FusionBegin$json = const {
|
|
||||||
'1': 'FusionBegin',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'tier', '3': 1, '4': 2, '5': 4, '10': 'tier'},
|
|
||||||
const {'1': 'covert_domain', '3': 2, '4': 2, '5': 12, '10': 'covertDomain'},
|
|
||||||
const {'1': 'covert_port', '3': 3, '4': 2, '5': 13, '10': 'covertPort'},
|
|
||||||
const {'1': 'covert_ssl', '3': 4, '4': 1, '5': 8, '10': 'covertSsl'},
|
|
||||||
const {'1': 'server_time', '3': 5, '4': 2, '5': 6, '10': 'serverTime'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `FusionBegin`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List fusionBeginDescriptor = $convert.base64Decode('CgtGdXNpb25CZWdpbhISCgR0aWVyGAEgAigEUgR0aWVyEiMKDWNvdmVydF9kb21haW4YAiACKAxSDGNvdmVydERvbWFpbhIfCgtjb3ZlcnRfcG9ydBgDIAIoDVIKY292ZXJ0UG9ydBIdCgpjb3ZlcnRfc3NsGAQgASgIUgljb3ZlcnRTc2wSHwoLc2VydmVyX3RpbWUYBSACKAZSCnNlcnZlclRpbWU=');
|
|
||||||
@$core.Deprecated('Use startRoundDescriptor instead')
|
|
||||||
const StartRound$json = const {
|
|
||||||
'1': 'StartRound',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'round_pubkey', '3': 1, '4': 2, '5': 12, '10': 'roundPubkey'},
|
|
||||||
const {'1': 'blind_nonce_points', '3': 2, '4': 3, '5': 12, '10': 'blindNoncePoints'},
|
|
||||||
const {'1': 'server_time', '3': 5, '4': 2, '5': 6, '10': 'serverTime'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `StartRound`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List startRoundDescriptor = $convert.base64Decode('CgpTdGFydFJvdW5kEiEKDHJvdW5kX3B1YmtleRgBIAIoDFILcm91bmRQdWJrZXkSLAoSYmxpbmRfbm9uY2VfcG9pbnRzGAIgAygMUhBibGluZE5vbmNlUG9pbnRzEh8KC3NlcnZlcl90aW1lGAUgAigGUgpzZXJ2ZXJUaW1l');
|
|
||||||
@$core.Deprecated('Use playerCommitDescriptor instead')
|
|
||||||
const PlayerCommit$json = const {
|
|
||||||
'1': 'PlayerCommit',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'initial_commitments', '3': 1, '4': 3, '5': 12, '10': 'initialCommitments'},
|
|
||||||
const {'1': 'excess_fee', '3': 2, '4': 2, '5': 4, '10': 'excessFee'},
|
|
||||||
const {'1': 'pedersen_total_nonce', '3': 3, '4': 2, '5': 12, '10': 'pedersenTotalNonce'},
|
|
||||||
const {'1': 'random_number_commitment', '3': 4, '4': 2, '5': 12, '10': 'randomNumberCommitment'},
|
|
||||||
const {'1': 'blind_sig_requests', '3': 5, '4': 3, '5': 12, '10': 'blindSigRequests'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `PlayerCommit`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List playerCommitDescriptor = $convert.base64Decode('CgxQbGF5ZXJDb21taXQSLwoTaW5pdGlhbF9jb21taXRtZW50cxgBIAMoDFISaW5pdGlhbENvbW1pdG1lbnRzEh0KCmV4Y2Vzc19mZWUYAiACKARSCWV4Y2Vzc0ZlZRIwChRwZWRlcnNlbl90b3RhbF9ub25jZRgDIAIoDFIScGVkZXJzZW5Ub3RhbE5vbmNlEjgKGHJhbmRvbV9udW1iZXJfY29tbWl0bWVudBgEIAIoDFIWcmFuZG9tTnVtYmVyQ29tbWl0bWVudBIsChJibGluZF9zaWdfcmVxdWVzdHMYBSADKAxSEGJsaW5kU2lnUmVxdWVzdHM=');
|
|
||||||
@$core.Deprecated('Use blindSigResponsesDescriptor instead')
|
|
||||||
const BlindSigResponses$json = const {
|
|
||||||
'1': 'BlindSigResponses',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'scalars', '3': 1, '4': 3, '5': 12, '10': 'scalars'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `BlindSigResponses`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List blindSigResponsesDescriptor = $convert.base64Decode('ChFCbGluZFNpZ1Jlc3BvbnNlcxIYCgdzY2FsYXJzGAEgAygMUgdzY2FsYXJz');
|
|
||||||
@$core.Deprecated('Use allCommitmentsDescriptor instead')
|
|
||||||
const AllCommitments$json = const {
|
|
||||||
'1': 'AllCommitments',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'initial_commitments', '3': 1, '4': 3, '5': 12, '10': 'initialCommitments'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `AllCommitments`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List allCommitmentsDescriptor = $convert.base64Decode('Cg5BbGxDb21taXRtZW50cxIvChNpbml0aWFsX2NvbW1pdG1lbnRzGAEgAygMUhJpbml0aWFsQ29tbWl0bWVudHM=');
|
|
||||||
@$core.Deprecated('Use covertComponentDescriptor instead')
|
|
||||||
const CovertComponent$json = const {
|
|
||||||
'1': 'CovertComponent',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'round_pubkey', '3': 1, '4': 1, '5': 12, '10': 'roundPubkey'},
|
|
||||||
const {'1': 'signature', '3': 2, '4': 2, '5': 12, '10': 'signature'},
|
|
||||||
const {'1': 'component', '3': 3, '4': 2, '5': 12, '10': 'component'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `CovertComponent`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List covertComponentDescriptor = $convert.base64Decode('Cg9Db3ZlcnRDb21wb25lbnQSIQoMcm91bmRfcHVia2V5GAEgASgMUgtyb3VuZFB1YmtleRIcCglzaWduYXR1cmUYAiACKAxSCXNpZ25hdHVyZRIcCgljb21wb25lbnQYAyACKAxSCWNvbXBvbmVudA==');
|
|
||||||
@$core.Deprecated('Use shareCovertComponentsDescriptor instead')
|
|
||||||
const ShareCovertComponents$json = const {
|
|
||||||
'1': 'ShareCovertComponents',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'components', '3': 4, '4': 3, '5': 12, '10': 'components'},
|
|
||||||
const {'1': 'skip_signatures', '3': 5, '4': 1, '5': 8, '10': 'skipSignatures'},
|
|
||||||
const {'1': 'session_hash', '3': 6, '4': 1, '5': 12, '10': 'sessionHash'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `ShareCovertComponents`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List shareCovertComponentsDescriptor = $convert.base64Decode('ChVTaGFyZUNvdmVydENvbXBvbmVudHMSHgoKY29tcG9uZW50cxgEIAMoDFIKY29tcG9uZW50cxInCg9za2lwX3NpZ25hdHVyZXMYBSABKAhSDnNraXBTaWduYXR1cmVzEiEKDHNlc3Npb25faGFzaBgGIAEoDFILc2Vzc2lvbkhhc2g=');
|
|
||||||
@$core.Deprecated('Use covertTransactionSignatureDescriptor instead')
|
|
||||||
const CovertTransactionSignature$json = const {
|
|
||||||
'1': 'CovertTransactionSignature',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'round_pubkey', '3': 1, '4': 1, '5': 12, '10': 'roundPubkey'},
|
|
||||||
const {'1': 'which_input', '3': 2, '4': 2, '5': 13, '10': 'whichInput'},
|
|
||||||
const {'1': 'txsignature', '3': 3, '4': 2, '5': 12, '10': 'txsignature'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `CovertTransactionSignature`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List covertTransactionSignatureDescriptor = $convert.base64Decode('ChpDb3ZlcnRUcmFuc2FjdGlvblNpZ25hdHVyZRIhCgxyb3VuZF9wdWJrZXkYASABKAxSC3JvdW5kUHVia2V5Eh8KC3doaWNoX2lucHV0GAIgAigNUgp3aGljaElucHV0EiAKC3R4c2lnbmF0dXJlGAMgAigMUgt0eHNpZ25hdHVyZQ==');
|
|
||||||
@$core.Deprecated('Use fusionResultDescriptor instead')
|
|
||||||
const FusionResult$json = const {
|
|
||||||
'1': 'FusionResult',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'ok', '3': 1, '4': 2, '5': 8, '10': 'ok'},
|
|
||||||
const {'1': 'txsignatures', '3': 2, '4': 3, '5': 12, '10': 'txsignatures'},
|
|
||||||
const {'1': 'bad_components', '3': 3, '4': 3, '5': 13, '10': 'badComponents'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `FusionResult`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List fusionResultDescriptor = $convert.base64Decode('CgxGdXNpb25SZXN1bHQSDgoCb2sYASACKAhSAm9rEiIKDHR4c2lnbmF0dXJlcxgCIAMoDFIMdHhzaWduYXR1cmVzEiUKDmJhZF9jb21wb25lbnRzGAMgAygNUg1iYWRDb21wb25lbnRz');
|
|
||||||
@$core.Deprecated('Use myProofsListDescriptor instead')
|
|
||||||
const MyProofsList$json = const {
|
|
||||||
'1': 'MyProofsList',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'encrypted_proofs', '3': 1, '4': 3, '5': 12, '10': 'encryptedProofs'},
|
|
||||||
const {'1': 'random_number', '3': 2, '4': 2, '5': 12, '10': 'randomNumber'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `MyProofsList`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List myProofsListDescriptor = $convert.base64Decode('CgxNeVByb29mc0xpc3QSKQoQZW5jcnlwdGVkX3Byb29mcxgBIAMoDFIPZW5jcnlwdGVkUHJvb2ZzEiMKDXJhbmRvbV9udW1iZXIYAiACKAxSDHJhbmRvbU51bWJlcg==');
|
|
||||||
@$core.Deprecated('Use theirProofsListDescriptor instead')
|
|
||||||
const TheirProofsList$json = const {
|
|
||||||
'1': 'TheirProofsList',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'proofs', '3': 1, '4': 3, '5': 11, '6': '.fusion.TheirProofsList.RelayedProof', '10': 'proofs'},
|
|
||||||
],
|
|
||||||
'3': const [TheirProofsList_RelayedProof$json],
|
|
||||||
};
|
|
||||||
|
|
||||||
@$core.Deprecated('Use theirProofsListDescriptor instead')
|
|
||||||
const TheirProofsList_RelayedProof$json = const {
|
|
||||||
'1': 'RelayedProof',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'encrypted_proof', '3': 1, '4': 2, '5': 12, '10': 'encryptedProof'},
|
|
||||||
const {'1': 'src_commitment_idx', '3': 2, '4': 2, '5': 13, '10': 'srcCommitmentIdx'},
|
|
||||||
const {'1': 'dst_key_idx', '3': 3, '4': 2, '5': 13, '10': 'dstKeyIdx'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `TheirProofsList`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List theirProofsListDescriptor = $convert.base64Decode('Cg9UaGVpclByb29mc0xpc3QSPAoGcHJvb2ZzGAEgAygLMiQuZnVzaW9uLlRoZWlyUHJvb2ZzTGlzdC5SZWxheWVkUHJvb2ZSBnByb29mcxqFAQoMUmVsYXllZFByb29mEicKD2VuY3J5cHRlZF9wcm9vZhgBIAIoDFIOZW5jcnlwdGVkUHJvb2YSLAoSc3JjX2NvbW1pdG1lbnRfaWR4GAIgAigNUhBzcmNDb21taXRtZW50SWR4Eh4KC2RzdF9rZXlfaWR4GAMgAigNUglkc3RLZXlJZHg=');
|
|
||||||
@$core.Deprecated('Use blamesDescriptor instead')
|
|
||||||
const Blames$json = const {
|
|
||||||
'1': 'Blames',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'blames', '3': 1, '4': 3, '5': 11, '6': '.fusion.Blames.BlameProof', '10': 'blames'},
|
|
||||||
],
|
|
||||||
'3': const [Blames_BlameProof$json],
|
|
||||||
};
|
|
||||||
|
|
||||||
@$core.Deprecated('Use blamesDescriptor instead')
|
|
||||||
const Blames_BlameProof$json = const {
|
|
||||||
'1': 'BlameProof',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'which_proof', '3': 1, '4': 2, '5': 13, '10': 'whichProof'},
|
|
||||||
const {'1': 'session_key', '3': 2, '4': 1, '5': 12, '9': 0, '10': 'sessionKey'},
|
|
||||||
const {'1': 'privkey', '3': 3, '4': 1, '5': 12, '9': 0, '10': 'privkey'},
|
|
||||||
const {'1': 'need_lookup_blockchain', '3': 4, '4': 1, '5': 8, '10': 'needLookupBlockchain'},
|
|
||||||
const {'1': 'blame_reason', '3': 5, '4': 1, '5': 9, '10': 'blameReason'},
|
|
||||||
],
|
|
||||||
'8': const [
|
|
||||||
const {'1': 'decrypter'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Blames`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List blamesDescriptor = $convert.base64Decode('CgZCbGFtZXMSMQoGYmxhbWVzGAEgAygLMhkuZnVzaW9uLkJsYW1lcy5CbGFtZVByb29mUgZibGFtZXMa0gEKCkJsYW1lUHJvb2YSHwoLd2hpY2hfcHJvb2YYASACKA1SCndoaWNoUHJvb2YSIQoLc2Vzc2lvbl9rZXkYAiABKAxIAFIKc2Vzc2lvbktleRIaCgdwcml2a2V5GAMgASgMSABSB3ByaXZrZXkSNAoWbmVlZF9sb29rdXBfYmxvY2tjaGFpbhgEIAEoCFIUbmVlZExvb2t1cEJsb2NrY2hhaW4SIQoMYmxhbWVfcmVhc29uGAUgASgJUgtibGFtZVJlYXNvbkILCglkZWNyeXB0ZXI=');
|
|
||||||
@$core.Deprecated('Use restartRoundDescriptor instead')
|
|
||||||
const RestartRound$json = const {
|
|
||||||
'1': 'RestartRound',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `RestartRound`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List restartRoundDescriptor = $convert.base64Decode('CgxSZXN0YXJ0Um91bmQ=');
|
|
||||||
@$core.Deprecated('Use errorDescriptor instead')
|
|
||||||
const Error$json = const {
|
|
||||||
'1': 'Error',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'message', '3': 1, '4': 1, '5': 9, '10': 'message'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Error`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List errorDescriptor = $convert.base64Decode('CgVFcnJvchIYCgdtZXNzYWdlGAEgASgJUgdtZXNzYWdl');
|
|
||||||
@$core.Deprecated('Use pingDescriptor instead')
|
|
||||||
const Ping$json = const {
|
|
||||||
'1': 'Ping',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Ping`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List pingDescriptor = $convert.base64Decode('CgRQaW5n');
|
|
||||||
@$core.Deprecated('Use oKDescriptor instead')
|
|
||||||
const OK$json = const {
|
|
||||||
'1': 'OK',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `OK`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List oKDescriptor = $convert.base64Decode('CgJPSw==');
|
|
||||||
@$core.Deprecated('Use clientMessageDescriptor instead')
|
|
||||||
const ClientMessage$json = const {
|
|
||||||
'1': 'ClientMessage',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'clienthello', '3': 1, '4': 1, '5': 11, '6': '.fusion.ClientHello', '9': 0, '10': 'clienthello'},
|
|
||||||
const {'1': 'joinpools', '3': 2, '4': 1, '5': 11, '6': '.fusion.JoinPools', '9': 0, '10': 'joinpools'},
|
|
||||||
const {'1': 'playercommit', '3': 3, '4': 1, '5': 11, '6': '.fusion.PlayerCommit', '9': 0, '10': 'playercommit'},
|
|
||||||
const {'1': 'myproofslist', '3': 5, '4': 1, '5': 11, '6': '.fusion.MyProofsList', '9': 0, '10': 'myproofslist'},
|
|
||||||
const {'1': 'blames', '3': 6, '4': 1, '5': 11, '6': '.fusion.Blames', '9': 0, '10': 'blames'},
|
|
||||||
],
|
|
||||||
'8': const [
|
|
||||||
const {'1': 'msg'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `ClientMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List clientMessageDescriptor = $convert.base64Decode('Cg1DbGllbnRNZXNzYWdlEjcKC2NsaWVudGhlbGxvGAEgASgLMhMuZnVzaW9uLkNsaWVudEhlbGxvSABSC2NsaWVudGhlbGxvEjEKCWpvaW5wb29scxgCIAEoCzIRLmZ1c2lvbi5Kb2luUG9vbHNIAFIJam9pbnBvb2xzEjoKDHBsYXllcmNvbW1pdBgDIAEoCzIULmZ1c2lvbi5QbGF5ZXJDb21taXRIAFIMcGxheWVyY29tbWl0EjoKDG15cHJvb2ZzbGlzdBgFIAEoCzIULmZ1c2lvbi5NeVByb29mc0xpc3RIAFIMbXlwcm9vZnNsaXN0EigKBmJsYW1lcxgGIAEoCzIOLmZ1c2lvbi5CbGFtZXNIAFIGYmxhbWVzQgUKA21zZw==');
|
|
||||||
@$core.Deprecated('Use serverMessageDescriptor instead')
|
|
||||||
const ServerMessage$json = const {
|
|
||||||
'1': 'ServerMessage',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'serverhello', '3': 1, '4': 1, '5': 11, '6': '.fusion.ServerHello', '9': 0, '10': 'serverhello'},
|
|
||||||
const {'1': 'tierstatusupdate', '3': 2, '4': 1, '5': 11, '6': '.fusion.TierStatusUpdate', '9': 0, '10': 'tierstatusupdate'},
|
|
||||||
const {'1': 'fusionbegin', '3': 3, '4': 1, '5': 11, '6': '.fusion.FusionBegin', '9': 0, '10': 'fusionbegin'},
|
|
||||||
const {'1': 'startround', '3': 4, '4': 1, '5': 11, '6': '.fusion.StartRound', '9': 0, '10': 'startround'},
|
|
||||||
const {'1': 'blindsigresponses', '3': 5, '4': 1, '5': 11, '6': '.fusion.BlindSigResponses', '9': 0, '10': 'blindsigresponses'},
|
|
||||||
const {'1': 'allcommitments', '3': 6, '4': 1, '5': 11, '6': '.fusion.AllCommitments', '9': 0, '10': 'allcommitments'},
|
|
||||||
const {'1': 'sharecovertcomponents', '3': 7, '4': 1, '5': 11, '6': '.fusion.ShareCovertComponents', '9': 0, '10': 'sharecovertcomponents'},
|
|
||||||
const {'1': 'fusionresult', '3': 8, '4': 1, '5': 11, '6': '.fusion.FusionResult', '9': 0, '10': 'fusionresult'},
|
|
||||||
const {'1': 'theirproofslist', '3': 9, '4': 1, '5': 11, '6': '.fusion.TheirProofsList', '9': 0, '10': 'theirproofslist'},
|
|
||||||
const {'1': 'restartround', '3': 14, '4': 1, '5': 11, '6': '.fusion.RestartRound', '9': 0, '10': 'restartround'},
|
|
||||||
const {'1': 'error', '3': 15, '4': 1, '5': 11, '6': '.fusion.Error', '9': 0, '10': 'error'},
|
|
||||||
],
|
|
||||||
'8': const [
|
|
||||||
const {'1': 'msg'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `ServerMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List serverMessageDescriptor = $convert.base64Decode('Cg1TZXJ2ZXJNZXNzYWdlEjcKC3NlcnZlcmhlbGxvGAEgASgLMhMuZnVzaW9uLlNlcnZlckhlbGxvSABSC3NlcnZlcmhlbGxvEkYKEHRpZXJzdGF0dXN1cGRhdGUYAiABKAsyGC5mdXNpb24uVGllclN0YXR1c1VwZGF0ZUgAUhB0aWVyc3RhdHVzdXBkYXRlEjcKC2Z1c2lvbmJlZ2luGAMgASgLMhMuZnVzaW9uLkZ1c2lvbkJlZ2luSABSC2Z1c2lvbmJlZ2luEjQKCnN0YXJ0cm91bmQYBCABKAsyEi5mdXNpb24uU3RhcnRSb3VuZEgAUgpzdGFydHJvdW5kEkkKEWJsaW5kc2lncmVzcG9uc2VzGAUgASgLMhkuZnVzaW9uLkJsaW5kU2lnUmVzcG9uc2VzSABSEWJsaW5kc2lncmVzcG9uc2VzEkAKDmFsbGNvbW1pdG1lbnRzGAYgASgLMhYuZnVzaW9uLkFsbENvbW1pdG1lbnRzSABSDmFsbGNvbW1pdG1lbnRzElUKFXNoYXJlY292ZXJ0Y29tcG9uZW50cxgHIAEoCzIdLmZ1c2lvbi5TaGFyZUNvdmVydENvbXBvbmVudHNIAFIVc2hhcmVjb3ZlcnRjb21wb25lbnRzEjoKDGZ1c2lvbnJlc3VsdBgIIAEoCzIULmZ1c2lvbi5GdXNpb25SZXN1bHRIAFIMZnVzaW9ucmVzdWx0EkMKD3RoZWlycHJvb2ZzbGlzdBgJIAEoCzIXLmZ1c2lvbi5UaGVpclByb29mc0xpc3RIAFIPdGhlaXJwcm9vZnNsaXN0EjoKDHJlc3RhcnRyb3VuZBgOIAEoCzIULmZ1c2lvbi5SZXN0YXJ0Um91bmRIAFIMcmVzdGFydHJvdW5kEiUKBWVycm9yGA8gASgLMg0uZnVzaW9uLkVycm9ySABSBWVycm9yQgUKA21zZw==');
|
|
||||||
@$core.Deprecated('Use covertMessageDescriptor instead')
|
|
||||||
const CovertMessage$json = const {
|
|
||||||
'1': 'CovertMessage',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'component', '3': 1, '4': 1, '5': 11, '6': '.fusion.CovertComponent', '9': 0, '10': 'component'},
|
|
||||||
const {'1': 'signature', '3': 2, '4': 1, '5': 11, '6': '.fusion.CovertTransactionSignature', '9': 0, '10': 'signature'},
|
|
||||||
const {'1': 'ping', '3': 3, '4': 1, '5': 11, '6': '.fusion.Ping', '9': 0, '10': 'ping'},
|
|
||||||
],
|
|
||||||
'8': const [
|
|
||||||
const {'1': 'msg'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `CovertMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List covertMessageDescriptor = $convert.base64Decode('Cg1Db3ZlcnRNZXNzYWdlEjcKCWNvbXBvbmVudBgBIAEoCzIXLmZ1c2lvbi5Db3ZlcnRDb21wb25lbnRIAFIJY29tcG9uZW50EkIKCXNpZ25hdHVyZRgCIAEoCzIiLmZ1c2lvbi5Db3ZlcnRUcmFuc2FjdGlvblNpZ25hdHVyZUgAUglzaWduYXR1cmUSIgoEcGluZxgDIAEoCzIMLmZ1c2lvbi5QaW5nSABSBHBpbmdCBQoDbXNn');
|
|
||||||
@$core.Deprecated('Use covertResponseDescriptor instead')
|
|
||||||
const CovertResponse$json = const {
|
|
||||||
'1': 'CovertResponse',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'ok', '3': 1, '4': 1, '5': 11, '6': '.fusion.OK', '9': 0, '10': 'ok'},
|
|
||||||
const {'1': 'error', '3': 15, '4': 1, '5': 11, '6': '.fusion.Error', '9': 0, '10': 'error'},
|
|
||||||
],
|
|
||||||
'8': const [
|
|
||||||
const {'1': 'msg'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `CovertResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List covertResponseDescriptor = $convert.base64Decode('Cg5Db3ZlcnRSZXNwb25zZRIcCgJvaxgBIAEoCzIKLmZ1c2lvbi5PS0gAUgJvaxIlCgVlcnJvchgPIAEoCzINLmZ1c2lvbi5FcnJvckgAUgVlcnJvckIFCgNtc2c=');
|
|
|
@ -1,281 +0,0 @@
|
||||||
/*
|
|
||||||
* Electron Cash - a lightweight Bitcoin Cash client
|
|
||||||
* CashFusion - an advanced coin anonymizer
|
|
||||||
*
|
|
||||||
* Copyright (C) 2020 Mark B. Lundeberg
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation files
|
|
||||||
* (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge,
|
|
||||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
||||||
* and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
syntax = "proto2";
|
|
||||||
|
|
||||||
package fusion;
|
|
||||||
|
|
||||||
// Some primitives
|
|
||||||
|
|
||||||
message InputComponent {
|
|
||||||
required bytes prev_txid = 1; // in 'reverse' order, just like in tx
|
|
||||||
required uint32 prev_index = 2;
|
|
||||||
required bytes pubkey = 3;
|
|
||||||
required uint64 amount = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OutputComponent {
|
|
||||||
required bytes scriptpubkey = 1;
|
|
||||||
required uint64 amount = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BlankComponent {
|
|
||||||
}
|
|
||||||
|
|
||||||
message Component {
|
|
||||||
required bytes salt_commitment = 1; // 32 bytes
|
|
||||||
oneof component {
|
|
||||||
InputComponent input = 2;
|
|
||||||
OutputComponent output = 3;
|
|
||||||
BlankComponent blank = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message InitialCommitment {
|
|
||||||
required bytes salted_component_hash = 1; // 32 byte hash
|
|
||||||
required bytes amount_commitment = 2; // uncompressed point
|
|
||||||
required bytes communication_key = 3; // compressed point
|
|
||||||
}
|
|
||||||
|
|
||||||
message Proof {
|
|
||||||
// During blame phase, messages of this form are encrypted and sent
|
|
||||||
// to a different player. It is already known which commitment this
|
|
||||||
// should apply to, so we only need to point at the component.
|
|
||||||
required fixed32 component_idx = 1;
|
|
||||||
required bytes salt = 2; // 32 bytes
|
|
||||||
required bytes pedersen_nonce = 3; // 32 bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Primary communication message types (and flow)
|
|
||||||
|
|
||||||
// Setup phase
|
|
||||||
|
|
||||||
message ClientHello { // from client
|
|
||||||
required bytes version = 1;
|
|
||||||
optional bytes genesis_hash = 2; // 32 byte hash (bitcoind little-endian memory order)
|
|
||||||
}
|
|
||||||
|
|
||||||
message ServerHello { // from server
|
|
||||||
repeated uint64 tiers = 1;
|
|
||||||
required uint32 num_components = 2;
|
|
||||||
required uint64 component_feerate = 4; // sats/kB
|
|
||||||
required uint64 min_excess_fee = 5; // sats
|
|
||||||
required uint64 max_excess_fee = 6; // sats
|
|
||||||
|
|
||||||
optional string donation_address = 15; // BCH Address "bitcoincash:qpx..."
|
|
||||||
}
|
|
||||||
|
|
||||||
message JoinPools { // from client
|
|
||||||
message PoolTag {
|
|
||||||
// These tags can be used to client to stop the server from including
|
|
||||||
// the client too many times in the same fusion. Thus, the client can
|
|
||||||
// connect many times without fear of fusing with themselves.
|
|
||||||
required bytes id = 1; // allowed up to 20 bytes
|
|
||||||
required uint32 limit = 2; // between 1 and 5 inclusive
|
|
||||||
optional bool no_ip = 3; // whether to do an IP-less tag -- this will collide with all other users, make sure it's random so you can't get DoSed.
|
|
||||||
}
|
|
||||||
repeated uint64 tiers = 1;
|
|
||||||
repeated PoolTag tags = 2; // at most five tags.
|
|
||||||
}
|
|
||||||
|
|
||||||
message TierStatusUpdate { // from server
|
|
||||||
message TierStatus {
|
|
||||||
// in future, we will want server to indicate 'remaining time' and mask number of players.
|
|
||||||
// note: if player is in queue then a status will be ommitted.
|
|
||||||
optional uint32 players = 1;
|
|
||||||
optional uint32 min_players = 2; // minimum required to start (may have delay to allow extra)
|
|
||||||
optional uint32 max_players = 3; // maximum allowed (immediate start)
|
|
||||||
optional uint32 time_remaining = 4;
|
|
||||||
}
|
|
||||||
map<uint64, TierStatus> statuses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FusionBegin { // from server
|
|
||||||
required uint64 tier = 1;
|
|
||||||
required bytes covert_domain = 2;
|
|
||||||
required uint32 covert_port = 3;
|
|
||||||
optional bool covert_ssl = 4;
|
|
||||||
required fixed64 server_time = 5; // server unix time when sending this message; can't be too far off from recipient's clock.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Fusion round (repeatable multiple times per connection)
|
|
||||||
|
|
||||||
message StartRound { // from server
|
|
||||||
required bytes round_pubkey = 1;
|
|
||||||
repeated bytes blind_nonce_points = 2;
|
|
||||||
required fixed64 server_time = 5; // server unix time when sending this message; can't be too far off from recipient's clock.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3
|
|
||||||
message PlayerCommit { // from client
|
|
||||||
repeated bytes initial_commitments = 1; // serialized InitialCommitment messages; server will repeat them later, verbatim.
|
|
||||||
required uint64 excess_fee = 2;
|
|
||||||
required bytes pedersen_total_nonce = 3; // 32 bytes
|
|
||||||
required bytes random_number_commitment = 4; // 32 bytes
|
|
||||||
repeated bytes blind_sig_requests = 5; // 32 byte scalars
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 4
|
|
||||||
message BlindSigResponses { // from server
|
|
||||||
repeated bytes scalars = 1; // 32 byte scalars
|
|
||||||
}
|
|
||||||
|
|
||||||
message AllCommitments {
|
|
||||||
// All the commitments from all players. At ~140 bytes per commitment and hundreds of commitments, this can be quite large, so it gets sent in its own message during the covert phase.
|
|
||||||
repeated bytes initial_commitments = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Phase 5
|
|
||||||
message CovertComponent { // from covert client
|
|
||||||
// The round key is used to identify the pool if needed
|
|
||||||
optional bytes round_pubkey = 1;
|
|
||||||
required bytes signature = 2;
|
|
||||||
required bytes component = 3; // bytes so that it can be signed and hashed verbatim
|
|
||||||
}
|
|
||||||
|
|
||||||
//Phase 6
|
|
||||||
message ShareCovertComponents { // from server
|
|
||||||
// This is a large message! 168 bytes per initial commitment, ~112 bytes per input component.
|
|
||||||
// Can easily reach 100 kB or more.
|
|
||||||
repeated bytes components = 4;
|
|
||||||
optional bool skip_signatures = 5; // if the server already sees a problem in submitted components
|
|
||||||
optional bytes session_hash = 6; // the server's calculation of session hash, so clients can crosscheck.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 7A
|
|
||||||
message CovertTransactionSignature { // from covert client
|
|
||||||
// The round key is used to identify the pool if needed
|
|
||||||
optional bytes round_pubkey = 1;
|
|
||||||
required uint32 which_input = 2;
|
|
||||||
required bytes txsignature = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 8
|
|
||||||
message FusionResult { // from server
|
|
||||||
required bool ok = 1;
|
|
||||||
repeated bytes txsignatures = 2; // if ok
|
|
||||||
repeated uint32 bad_components = 3; // if not ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 9
|
|
||||||
message MyProofsList { // from client
|
|
||||||
repeated bytes encrypted_proofs = 1;
|
|
||||||
required bytes random_number = 2; // the number we committed to, back in phase 3
|
|
||||||
}
|
|
||||||
|
|
||||||
message TheirProofsList { // from server
|
|
||||||
message RelayedProof {
|
|
||||||
required bytes encrypted_proof = 1;
|
|
||||||
required uint32 src_commitment_idx = 2; // which of the commitments is being proven (index in full list)
|
|
||||||
required uint32 dst_key_idx = 3; // which of the recipient's keys will unlock the encryption (index in player list)
|
|
||||||
}
|
|
||||||
repeated RelayedProof proofs = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 10
|
|
||||||
message Blames { // from client
|
|
||||||
message BlameProof {
|
|
||||||
required uint32 which_proof = 1;
|
|
||||||
oneof decrypter {
|
|
||||||
bytes session_key = 2; // 32 byte, preferred if the proof decryption works at all
|
|
||||||
bytes privkey = 3; // 32 byte scalar
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some errors can only be discovered by checking the blockchain,
|
|
||||||
// Namely, if an input UTXO is missing/spent/unconfirmed/different
|
|
||||||
// scriptpubkey/different amount, than indicated.
|
|
||||||
optional bool need_lookup_blockchain = 4;
|
|
||||||
|
|
||||||
// The client can indicate why it thinks the blame is deserved. In
|
|
||||||
// case the server finds no issue, this string might help for debugging.
|
|
||||||
optional string blame_reason = 5;
|
|
||||||
}
|
|
||||||
repeated BlameProof blames = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final message of the round
|
|
||||||
message RestartRound {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal error from server, likely we did something wrong (it will disconnect us, but the message may help debugging).
|
|
||||||
message Error {
|
|
||||||
optional string message = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple ping, as a keepalive.
|
|
||||||
message Ping {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple acknowledgement, nothing more to say.
|
|
||||||
message OK {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Primary communication channel types
|
|
||||||
|
|
||||||
message ClientMessage {
|
|
||||||
oneof msg {
|
|
||||||
ClientHello clienthello = 1;
|
|
||||||
JoinPools joinpools = 2;
|
|
||||||
PlayerCommit playercommit = 3;
|
|
||||||
MyProofsList myproofslist = 5;
|
|
||||||
Blames blames = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message ServerMessage {
|
|
||||||
oneof msg {
|
|
||||||
ServerHello serverhello = 1;
|
|
||||||
TierStatusUpdate tierstatusupdate = 2;
|
|
||||||
FusionBegin fusionbegin = 3;
|
|
||||||
StartRound startround = 4;
|
|
||||||
BlindSigResponses blindsigresponses = 5;
|
|
||||||
AllCommitments allcommitments = 6;
|
|
||||||
ShareCovertComponents sharecovertcomponents = 7;
|
|
||||||
FusionResult fusionresult = 8;
|
|
||||||
TheirProofsList theirproofslist = 9;
|
|
||||||
|
|
||||||
RestartRound restartround = 14;
|
|
||||||
Error error = 15;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message CovertMessage { // client -> server, covertly
|
|
||||||
oneof msg {
|
|
||||||
CovertComponent component = 1;
|
|
||||||
CovertTransactionSignature signature = 2;
|
|
||||||
Ping ping = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message CovertResponse { // server -> a covert client
|
|
||||||
oneof msg {
|
|
||||||
OK ok = 1;
|
|
||||||
Error error = 15;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
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".)
|
|
||||||
//
|
|
||||||
//Deny server that asks for more than this component feerate (sat/kbyte).
|
|
||||||
static const MAX_COMPONENT_FEERATE = 5000;
|
|
||||||
//The largest 'excess fee' that we are willing to pay in a fusion (fees beyond
|
|
||||||
//those needed to pay for our components' inclusion)
|
|
||||||
static const MAX_EXCESS_FEE = 10000;
|
|
||||||
// Even if the server allows more, put at most this many inputs+outputs+blanks
|
|
||||||
static const MAX_COMPONENTS = 40;
|
|
||||||
// The largest total fee we are willing to pay (our contribution to transaction
|
|
||||||
// size should not exceed 7 kB even with 40 largest components).
|
|
||||||
static const MAX_FEE = MAX_COMPONENT_FEERATE * 7 + MAX_EXCESS_FEE;
|
|
||||||
// For privacy reasons, don't submit less than this many distinct tx components.
|
|
||||||
// (distinct tx inputs, and tx outputs)
|
|
||||||
static const MIN_TX_COMPONENTS = 11;
|
|
||||||
|
|
||||||
static const MIN_OUTPUT = 10000;
|
|
||||||
|
|
||||||
static const COVERT_CONNECT_TIMEOUT = 15.0;
|
|
||||||
static const COVERT_CONNECT_WINDOW = 15.0;
|
|
||||||
static const COVERT_SUBMIT_TIMEOUT = 3.0;
|
|
||||||
static const COVERT_SUBMIT_WINDOW = 5.0;
|
|
||||||
|
|
||||||
static const COVERT_CONNECT_SPARES = 6;
|
|
||||||
|
|
||||||
static const MAX_CLOCK_DISCREPANCY = 5.0;
|
|
||||||
|
|
||||||
static const WARMUP_TIME = 30.0;
|
|
||||||
static const WARMUP_SLOP = 3.0;
|
|
||||||
|
|
||||||
static const TS_EXPECTING_COMMITMENTS = 3.0;
|
|
||||||
|
|
||||||
static const T_START_COMPS = 5.0;
|
|
||||||
|
|
||||||
static const TS_EXPECTING_COVERT_COMPONENTS = 15.0;
|
|
||||||
|
|
||||||
static const T_START_SIGS = 20.0;
|
|
||||||
|
|
||||||
static const TS_EXPECTING_COVERT_SIGNATURES = 30.0;
|
|
||||||
|
|
||||||
static const T_EXPECTING_CONCLUSION = 35.0;
|
|
||||||
|
|
||||||
static const T_START_CLOSE = 45.0;
|
|
||||||
static const T_START_CLOSE_BLAME = 80.0;
|
|
||||||
|
|
||||||
static const STANDARD_TIMEOUT = 3.0;
|
|
||||||
static const BLAME_VERIFY_TIME = 5.0;
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
class SocketWrapper {
|
|
||||||
late Socket _socket;
|
|
||||||
final String serverIP;
|
|
||||||
final int serverPort;
|
|
||||||
|
|
||||||
late Stream<List<int>>
|
|
||||||
_receiveStream; // create a field for the broadcast stream
|
|
||||||
|
|
||||||
SocketWrapper(this.serverIP, this.serverPort);
|
|
||||||
Socket get socket => _socket;
|
|
||||||
|
|
||||||
Stream<List<int>> get receiveStream =>
|
|
||||||
_receiveStream; // expose the stream with a getter
|
|
||||||
|
|
||||||
Future<void> connect() async {
|
|
||||||
_socket = await Socket.connect(serverIP, serverPort);
|
|
||||||
_receiveStream =
|
|
||||||
_socket.asBroadcastStream(); // initialize the broadcast stream
|
|
||||||
_socket.done.then((_) {
|
|
||||||
print('......Socket has been closed');
|
|
||||||
});
|
|
||||||
_socket.handleError((error) {
|
|
||||||
print('Socket error: $error');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void status() {
|
|
||||||
if (_socket != null) {
|
|
||||||
print(
|
|
||||||
"Socket connected to ${_socket.remoteAddress.address}:${_socket.remotePort}");
|
|
||||||
} else {
|
|
||||||
print("Socket is not connected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> send(List<int> data) async {
|
|
||||||
if (_socket != null) {
|
|
||||||
_socket.add(data);
|
|
||||||
await _socket.flush();
|
|
||||||
} else {
|
|
||||||
// handle error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
_socket.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,329 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart' as crypto;
|
|
||||||
import 'package:pointycastle/ecc/api.dart';
|
|
||||||
|
|
||||||
import 'fusion.pb.dart';
|
|
||||||
import 'protocol.dart';
|
|
||||||
|
|
||||||
class Address {
|
|
||||||
String addr = "";
|
|
||||||
|
|
||||||
Address(
|
|
||||||
{required this.addr}); // Constructor updated to accept addr as a named parameter
|
|
||||||
|
|
||||||
Address._create({required this.addr});
|
|
||||||
|
|
||||||
static Address fromScriptPubKey(List<int> scriptPubKey) {
|
|
||||||
// This is just a placeholder code
|
|
||||||
String addr = ""; // This should be computed from the scriptPubKey
|
|
||||||
return Address(addr: addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public constructor for testing
|
|
||||||
static Address fromString(String address) {
|
|
||||||
return Address._create(addr: address);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int> toScript() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Tuple<T1, T2> {
|
|
||||||
T1 item1;
|
|
||||||
T2 item2;
|
|
||||||
|
|
||||||
Tuple(this.item1, this.item2);
|
|
||||||
|
|
||||||
set setItem1(T1 value) {
|
|
||||||
this.item1 = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
set setItem2(T2 value) {
|
|
||||||
this.item2 = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Util {
|
|
||||||
static Uint8List hexToBytes(String hex) {
|
|
||||||
var result = new Uint8List(hex.length ~/ 2);
|
|
||||||
for (var i = 0; i < hex.length; i += 2) {
|
|
||||||
var byte = int.parse(hex.substring(i, i + 2), radix: 16);
|
|
||||||
result[i ~/ 2] = byte;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void checkInputElectrumX(InputComponent inputComponent) {
|
|
||||||
// Implementation needed here
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
static int randPosition(Uint8List seed, int numPositions, int counter) {
|
|
||||||
// counter to bytes
|
|
||||||
var counterBytes = Uint8List(4);
|
|
||||||
var counterByteData = ByteData.sublistView(counterBytes);
|
|
||||||
counterByteData.setInt32(0, counter, Endian.big);
|
|
||||||
|
|
||||||
// hash the seed and counter
|
|
||||||
var digest = crypto.sha256.convert([...seed, ...counterBytes]);
|
|
||||||
|
|
||||||
// take the first 8 bytes
|
|
||||||
var first8Bytes = digest.bytes.take(8).toList();
|
|
||||||
var int64 = ByteData.sublistView(Uint8List.fromList(first8Bytes))
|
|
||||||
.getUint64(0, Endian.big);
|
|
||||||
|
|
||||||
// perform the modulo operation
|
|
||||||
return ((int64 * numPositions) >> 64).toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<String> pubkeysFromPrivkey(String privkey) {
|
|
||||||
// This is a placeholder implementation.
|
|
||||||
return ['public_key1_dummy', 'public_key2_dummy'];
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dustLimit(int length) {
|
|
||||||
// This is a dummy implementation.
|
|
||||||
return 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Address getAddressFromOutputScript(Uint8List scriptpubkey) {
|
|
||||||
// Dummy implementation...
|
|
||||||
|
|
||||||
// Throw exception if this is not a standard P2PKH address!
|
|
||||||
|
|
||||||
return Address.fromString('dummy_address');
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool schnorrVerify(
|
|
||||||
ECPoint pubkey, List<int> signature, Uint8List messageHash) {
|
|
||||||
// Implementation needed: actual Schnorr signature verification
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String formatSatoshis(sats, {int numZeros = 8}) {
|
|
||||||
// To implement
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
static void updateWalletLabel(String txid, String label) {
|
|
||||||
// Call the wallet layer.
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uint8List getRandomBytes(int length) {
|
|
||||||
final rand = Random.secure();
|
|
||||||
final bytes = Uint8List(length);
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
bytes[i] = rand.nextInt(256);
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<List<T>> zip<T>(List<T> list1, List<T> list2) {
|
|
||||||
int length = min(list1.length, list2.length);
|
|
||||||
return List<List<T>>.generate(length, (i) => [list1[i], list2[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<int> 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);
|
|
||||||
|
|
||||||
// Define constants
|
|
||||||
const version = Protocol.VERSION;
|
|
||||||
const cashFusionSession = "Cash Fusion Session";
|
|
||||||
|
|
||||||
// Creating the list of bytes
|
|
||||||
List<int> elements = [];
|
|
||||||
elements.addAll(utf8.encode(cashFusionSession));
|
|
||||||
elements.addAll(utf8.encode(version));
|
|
||||||
elements.addAll(tierBytes.buffer.asInt8List());
|
|
||||||
elements.addAll(covertDomainB);
|
|
||||||
elements.addAll(covertPortBytes.buffer.asInt8List());
|
|
||||||
elements.add(covertSsl ? 1 : 0);
|
|
||||||
elements.addAll(beginTimeBytes.buffer.asInt8List());
|
|
||||||
|
|
||||||
// Hashing the concatenated elements
|
|
||||||
var digest = crypto.sha256.convert(elements);
|
|
||||||
|
|
||||||
return digest.bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<int> calcRoundHash(
|
|
||||||
List<int> lastHash,
|
|
||||||
List<int> roundPubkey,
|
|
||||||
int roundTime,
|
|
||||||
List<List<int>> allCommitments,
|
|
||||||
List<List<int>> allComponents) {
|
|
||||||
return listHash([
|
|
||||||
utf8.encode('Cash Fusion Round'),
|
|
||||||
lastHash,
|
|
||||||
roundPubkey,
|
|
||||||
bigIntToBytes(BigInt.from(roundTime)),
|
|
||||||
listHash(allCommitments),
|
|
||||||
listHash(allComponents),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<int> listHash(Iterable<List<int>> iterable) {
|
|
||||||
var bytes = <int>[];
|
|
||||||
|
|
||||||
for (var x in iterable) {
|
|
||||||
var length = ByteData(4)..setUint32(0, x.length, Endian.big);
|
|
||||||
bytes.addAll(length.buffer.asUint8List());
|
|
||||||
bytes.addAll(x);
|
|
||||||
}
|
|
||||||
return crypto.sha256.convert(bytes).bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uint8List get_current_genesis_hash() {
|
|
||||||
var GENESIS =
|
|
||||||
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
|
||||||
var _lastGenesisHash = hexToBytes(GENESIS).reversed.toList();
|
|
||||||
return Uint8List.fromList(_lastGenesisHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<Address> unreserve_change_address(Address addr) {
|
|
||||||
//implement later based on wallet.
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<Address> reserve_change_addresses(int number_addresses) {
|
|
||||||
//implement later based on wallet.
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool walletHasTransaction(String txid) {
|
|
||||||
// implement later based on wallet.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uint8List bigIntToBytes(BigInt bigInt) {
|
|
||||||
return Uint8List.fromList(
|
|
||||||
bigInt.toRadixString(16).padLeft(32, '0').codeUnits);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Tuple<Uint8List, Uint8List> 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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
var bytes = bitLength ~/ 8; // floor division
|
|
||||||
var remBit = bitLength % 8;
|
|
||||||
|
|
||||||
// Generate random BigInt
|
|
||||||
List<int> rnd = List<int>.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);
|
|
||||||
|
|
||||||
return privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional helper function to convert bytes to hex
|
|
||||||
static String bytesToHex(Uint8List bytes) {
|
|
||||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
|
||||||
}
|
|
||||||
|
|
||||||
static BigInt bytesToBigInt(Uint8List bytes) {
|
|
||||||
String hexString = bytesToHex(bytes);
|
|
||||||
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<int>.generate(nbytes, (i) => _random.nextInt(256)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int componentFee(int size, int feerate) {
|
|
||||||
// feerate in sat/kB
|
|
||||||
// size and feerate should both be integer
|
|
||||||
// fee is always rounded up
|
|
||||||
return ((size * feerate) + 999) ~/ 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ECPoint ser_to_point(
|
|
||||||
Uint8List serializedPoint, ECDomainParameters params) {
|
|
||||||
var point = params.curve.decodePoint(serializedPoint);
|
|
||||||
if (point == null) {
|
|
||||||
throw FormatException('Point decoding failed');
|
|
||||||
}
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uint8List point_to_ser(ECPoint point, bool compress) {
|
|
||||||
return point.getEncoded(compress);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BigInt secureRandomBigInt(int bitLength) {
|
|
||||||
final random = Random.secure();
|
|
||||||
final bytes = (bitLength + 7) ~/ 8; // ceil division
|
|
||||||
final Uint8List randomBytes = Uint8List(bytes);
|
|
||||||
|
|
||||||
for (int i = 0; i < bytes; i++) {
|
|
||||||
randomBytes[i] = random.nextInt(256);
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInt randomNumber = BigInt.parse(
|
|
||||||
randomBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(),
|
|
||||||
radix: 16);
|
|
||||||
return randomNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ECPoint combinePubKeys(List<ECPoint> pubKeys) {
|
|
||||||
if (pubKeys.isEmpty) throw ArgumentError('pubKeys cannot be empty');
|
|
||||||
|
|
||||||
ECPoint combined = pubKeys.first.curve.infinity!;
|
|
||||||
for (var pubKey in pubKeys) {
|
|
||||||
combined = (combined + pubKey)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (combined.isInfinity) {
|
|
||||||
throw Exception('Combined point is at infinity');
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isPointOnCurve(ECPoint point, ECCurve curve) {
|
|
||||||
var x = point.x!.toBigInteger()!;
|
|
||||||
var y = point.y!.toBigInteger()!;
|
|
||||||
var a = curve.a!.toBigInteger()!;
|
|
||||||
var b = curve.b!.toBigInteger()!;
|
|
||||||
|
|
||||||
// Calculate the left and right sides of the equation
|
|
||||||
var left = y * y;
|
|
||||||
var right = (x * x * x) + (a * x) + b;
|
|
||||||
|
|
||||||
// Check if the point is on the curve
|
|
||||||
return left == right;
|
|
||||||
}
|
|
||||||
} // END OF CLASS
|
|
|
@ -1,301 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
||||||
class ValidationError implements Exception {
|
|
||||||
final String message;
|
|
||||||
ValidationError(this.message);
|
|
||||||
@override
|
|
||||||
String toString() => 'Validation error: $message';
|
|
||||||
}
|
|
||||||
|
|
||||||
int componentContrib(pb.Component component, int feerate) {
|
|
||||||
if (component.hasInput()) {
|
|
||||||
var inp = Input.fromInputComponent(component.input);
|
|
||||||
return inp.amount.toInt() - Util.componentFee(inp.sizeOfInput(), feerate);
|
|
||||||
} else if (component.hasOutput()) {
|
|
||||||
var out = Output.fromOutputComponent(component.output);
|
|
||||||
return -out.amount.toInt() - Util.componentFee(out.sizeOfOutput(), feerate);
|
|
||||||
} else if (component.hasBlank()) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
throw ValidationError('Invalid component type');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void check(bool condition, String failMessage) {
|
|
||||||
if (!condition) {
|
|
||||||
throw ValidationError(failMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic protoStrictParse(dynamic msg, List<int> blob) {
|
|
||||||
try {
|
|
||||||
if (msg.mergeFromBuffer(blob) != blob.length) {
|
|
||||||
throw ArgumentError('DecodeError');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw ArgumentError('ValidationError: decode error');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!msg.isInitialized()) {
|
|
||||||
throw ArgumentError('missing fields');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protobuf in dart does not support 'unknownFields' method
|
|
||||||
// if (!msg.unknownFields.isEmpty) {
|
|
||||||
// throw ArgumentError('has extra fields');
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (msg.writeToBuffer().length != blob.length) {
|
|
||||||
throw ArgumentError('encoding too long');
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<pb.InitialCommitment> 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(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");
|
|
||||||
|
|
||||||
List<pb.InitialCommitment> commitMessages = [];
|
|
||||||
for (var cblob in msg.initialCommitments) {
|
|
||||||
pb.InitialCommitment cmsg = protoStrictParse(pb.InitialCommitment(), cblob);
|
|
||||||
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");
|
|
||||||
commitMessages.add(cmsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
var claimedCommit;
|
|
||||||
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)));
|
|
||||||
|
|
||||||
check(pointsum == claimedCommit.PUncompressed,
|
|
||||||
"pedersen commitment mismatch");
|
|
||||||
} catch (e) {
|
|
||||||
throw ValidationError("pedersen commitment verification error");
|
|
||||||
}
|
|
||||||
check(
|
|
||||||
pointsum == claimedCommit.PUncompressed, "pedersen commitment mismatch");
|
|
||||||
return commitMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
Tuple<String, int> 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),
|
|
||||||
"bad message signature");
|
|
||||||
|
|
||||||
var cmsg = protoStrictParse(pb.Component(), msg.component);
|
|
||||||
check(cmsg.saltCommitment.length == 32, "bad salt commitment");
|
|
||||||
|
|
||||||
String sortKey;
|
|
||||||
|
|
||||||
if (cmsg.hasInput()) {
|
|
||||||
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 == 65 && inp.pubkey[0] == 4),
|
|
||||||
"bad pubkey");
|
|
||||||
sortKey = 'i' +
|
|
||||||
String.fromCharCodes(inp.prevTxid.reversed) +
|
|
||||||
inp.prevIndex.toString() +
|
|
||||||
String.fromCharCodes(cmsg.saltCommitment);
|
|
||||||
} else if (cmsg.hasOutput()) {
|
|
||||||
var out = cmsg.output;
|
|
||||||
Address addr;
|
|
||||||
// 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");
|
|
||||||
sortKey = 'o' +
|
|
||||||
out.amount.toString() +
|
|
||||||
String.fromCharCodes(out.scriptpubkey) +
|
|
||||||
String.fromCharCodes(cmsg.saltCommitment);
|
|
||||||
} else if (cmsg.hasBlank()) {
|
|
||||||
sortKey = 'b' + String.fromCharCodes(cmsg.saltCommitment);
|
|
||||||
} else {
|
|
||||||
throw ValidationError('missing component details');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Tuple(sortKey, componentContrib(cmsg, componentFeerate));
|
|
||||||
}
|
|
||||||
|
|
||||||
pb.InputComponent? validateProofInternal(
|
|
||||||
Uint8List proofBlob,
|
|
||||||
pb.InitialCommitment commitment,
|
|
||||||
List<Uint8List> allComponents,
|
|
||||||
List<int> 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) {
|
|
||||||
throw Exception('Failed to decode point');
|
|
||||||
}
|
|
||||||
ECPoint H = HMaybe;
|
|
||||||
PedersenSetup setup = PedersenSetup(H);
|
|
||||||
|
|
||||||
var msg = protoStrictParse(pb.Proof(), proofBlob);
|
|
||||||
|
|
||||||
Uint8List componentBlob;
|
|
||||||
try {
|
|
||||||
componentBlob = allComponents[msg.componentIdx];
|
|
||||||
} catch (e) {
|
|
||||||
throw ValidationError("component index out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
check(!badComponents.contains(msg.componentIdx), "component in bad list");
|
|
||||||
|
|
||||||
var comp = pb.Component();
|
|
||||||
comp.mergeFromBuffer(componentBlob);
|
|
||||||
assert(comp.isInitialized());
|
|
||||||
|
|
||||||
check(msg.salt.length == 32, "salt wrong length");
|
|
||||||
check(
|
|
||||||
Util.sha256(msg.salt) == comp.saltCommitment,
|
|
||||||
"salt commitment mismatch",
|
|
||||||
);
|
|
||||||
check(
|
|
||||||
Util.sha256(Uint8List.fromList([...msg.salt, ...componentBlob])) ==
|
|
||||||
commitment.saltedComponentHash,
|
|
||||||
"salted component hash mismatch",
|
|
||||||
);
|
|
||||||
|
|
||||||
var contrib = componentContrib(comp, componentFeerate);
|
|
||||||
|
|
||||||
var PCommitted = commitment.amountCommitment;
|
|
||||||
|
|
||||||
var claimedCommit = setup.commit(
|
|
||||||
BigInt.from(contrib),
|
|
||||||
nonce: Util.bytesToBigInt(msg.pedersenNonce),
|
|
||||||
);
|
|
||||||
|
|
||||||
check(
|
|
||||||
Uint8List.fromList(PCommitted) == claimedCommit.PUncompressed,
|
|
||||||
"pedersen commitment mismatch",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (comp.hasInput()) {
|
|
||||||
return comp.input;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> validateBlame(
|
|
||||||
pb.Blames_BlameProof blame,
|
|
||||||
Uint8List encProof,
|
|
||||||
Uint8List srcCommitBlob,
|
|
||||||
Uint8List destCommitBlob,
|
|
||||||
List<Uint8List> allComponents,
|
|
||||||
List<int> badComponents,
|
|
||||||
int componentFeerate,
|
|
||||||
) async {
|
|
||||||
var destCommit = pb.InitialCommitment();
|
|
||||||
destCommit.mergeFromBuffer(destCommitBlob);
|
|
||||||
var destPubkey = destCommit.communicationKey;
|
|
||||||
|
|
||||||
var srcCommit = pb.InitialCommitment();
|
|
||||||
srcCommit.mergeFromBuffer(srcCommitBlob);
|
|
||||||
|
|
||||||
var decrypter = blame.whichDecrypter();
|
|
||||||
ECDomainParameters params = ECDomainParameters('secp256k1');
|
|
||||||
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 privateKey = ECPrivateKey(privkeyBigInt, params); // Create ECPrivateKey
|
|
||||||
var pubkeys = Util.pubkeysFromPrivkey(privkeyHexStr);
|
|
||||||
check(destCommit.communicationKey == pubkeys[1], 'bad blame privkey');
|
|
||||||
try {
|
|
||||||
Encrypt.decrypt(encProof, privateKey);
|
|
||||||
} catch (e) {
|
|
||||||
return 'undecryptable';
|
|
||||||
}
|
|
||||||
throw ValidationError('blame gave privkey but decryption worked');
|
|
||||||
} else if (decrypter != pb.Blames_BlameProof_Decrypter.sessionKey) {
|
|
||||||
throw ValidationError('unknown blame decrypter');
|
|
||||||
}
|
|
||||||
var key = Uint8List.fromList(blame.sessionKey);
|
|
||||||
check(key.length == 32, 'bad blame session key');
|
|
||||||
Uint8List proofBlob;
|
|
||||||
try {
|
|
||||||
proofBlob = await Encrypt.decryptWithSymmkey(encProof, key);
|
|
||||||
} catch (e) {
|
|
||||||
throw ValidationError('bad blame session key');
|
|
||||||
}
|
|
||||||
pb.InputComponent? inpComp;
|
|
||||||
try {
|
|
||||||
inpComp = validateProofInternal(
|
|
||||||
proofBlob,
|
|
||||||
srcCommit,
|
|
||||||
allComponents,
|
|
||||||
badComponents,
|
|
||||||
componentFeerate,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return e.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!blame.needLookupBlockchain) {
|
|
||||||
throw ValidationError(
|
|
||||||
'blame indicated internal inconsistency, none found!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inpComp == null) {
|
|
||||||
throw ValidationError(
|
|
||||||
'blame indicated blockchain error on a non-input component');
|
|
||||||
}
|
|
||||||
|
|
||||||
return inpComp;
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fusiondart/fusion.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/db/isar/main_db.dart';
|
import 'package:stackwallet/db/isar/main_db.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
||||||
import 'package:stackwallet/services/cashfusion/fusion.dart';
|
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
|
||||||
mixin FusionInterface {
|
mixin FusionInterface {
|
||||||
|
@ -130,6 +130,7 @@ mixin FusionInterface {
|
||||||
|
|
||||||
Future<void> refreshFusion() {
|
Future<void> refreshFusion() {
|
||||||
// TODO
|
// TODO
|
||||||
throw UnimplementedError("TODO refreshFusion eg look up number of fusion participants connected/coordinating");
|
throw UnimplementedError(
|
||||||
|
"TODO refreshFusion eg look up number of fusion participants connected/coordinating");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -761,6 +761,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
fusiondart:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: fusiondart
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1870,5 +1877,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.2 <4.0.0"
|
dart: ">=3.0.3 <4.0.0"
|
||||||
flutter: ">=3.10.3"
|
flutter: ">=3.10.3"
|
||||||
|
|
|
@ -139,6 +139,9 @@ dependencies:
|
||||||
nanodart: ^2.0.0
|
nanodart: ^2.0.0
|
||||||
basic_utils: ^5.5.4
|
basic_utils: ^5.5.4
|
||||||
stellar_flutter_sdk: ^1.6.0
|
stellar_flutter_sdk: ^1.6.0
|
||||||
|
fusiondart:
|
||||||
|
path: ./fusiondart
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue