mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-24 12:29:37 +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"]
|
||||
path = crypto_plugins/flutter_liblelantus
|
||||
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 'package:fusiondart/fusion.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/isar/main_db.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';
|
||||
|
||||
mixin FusionInterface {
|
||||
|
@ -130,6 +130,7 @@ mixin FusionInterface {
|
|||
|
||||
Future<void> refreshFusion() {
|
||||
// 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
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fusiondart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: fusiondart
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1870,5 +1877,5 @@ packages:
|
|||
source: hosted
|
||||
version: "1.0.0"
|
||||
sdks:
|
||||
dart: ">=3.0.2 <4.0.0"
|
||||
dart: ">=3.0.3 <4.0.0"
|
||||
flutter: ">=3.10.3"
|
||||
|
|
|
@ -139,6 +139,9 @@ dependencies:
|
|||
nanodart: ^2.0.0
|
||||
basic_utils: ^5.5.4
|
||||
stellar_flutter_sdk: ^1.6.0
|
||||
fusiondart:
|
||||
path: ./fusiondart
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue