Merge branch 'fusiondart' into origin/fusion

This commit is contained in:
Josh Babb 2023-07-27 16:15:48 -05:00
commit cb9fa5a5b0
21 changed files with 18 additions and 9323 deletions

3
.gitmodules vendored
View file

@ -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

@ -0,0 +1 @@
Subproject commit d7e42820f0511636c47dd91e9c4f425f5f9d5be3

View file

@ -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()}');
}
}
}

View file

@ -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

View file

@ -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}).");
}
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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

View file

@ -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=');

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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");
} }
} }

View file

@ -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"

View file

@ -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: