general idea and structure together
This commit is contained in:
parent
d0dae05e6e
commit
c0c744ab35
31 changed files with 445 additions and 0 deletions
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 1.0.0
|
||||
|
||||
- Initial version.
|
30
analysis_options.yaml
Normal file
30
analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
7
bin/torchat_node.dart
Normal file
7
bin/torchat_node.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
import 'package:torchat/core_api/torchat_service.dart';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
final service = TorChatService();
|
||||
await service.startNode(); // Boots Tor, gRPC server, discovery loop
|
||||
print('TorChat node running. Press Ctrl-C to exit.');
|
||||
}
|
0
bin/torchat_rest_api.dart
Normal file
0
bin/torchat_rest_api.dart
Normal file
0
lib/core_api/message_handler.dart
Normal file
0
lib/core_api/message_handler.dart
Normal file
0
lib/core_api/torchat_server.dart
Normal file
0
lib/core_api/torchat_server.dart
Normal file
25
lib/core_api/torchat_service.dart
Normal file
25
lib/core_api/torchat_service.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
import 'package:torchat/src/p2p/grpc/server/grpc_server.dart';
|
||||
import 'package:torchat/src/p2p/message_queue/outgoing_queue.dart';
|
||||
import 'package:torchat/src/p2p/sync_manager.dart';
|
||||
|
||||
class TorChatService {
|
||||
final OutgoingQueue _queue = OutgoingQueue();
|
||||
late final GrpcServerRunner _grpcServer;
|
||||
late final SyncManager _sync;
|
||||
|
||||
Future<void> startNode() async {
|
||||
// TODO: start Tor/Arti here and open hidden service
|
||||
_grpcServer = GrpcServerRunner();
|
||||
await _grpcServer.start(_queue);
|
||||
|
||||
_sync = SyncManager(_queue);
|
||||
_sync.start();
|
||||
|
||||
// TODO: bootstrap peer discovery loop
|
||||
}
|
||||
|
||||
Future<void> stopNode() async {
|
||||
await _grpcServer.stop();
|
||||
_sync.stop();
|
||||
}
|
||||
}
|
0
lib/src/encryption/ed25519_utils.dart
Normal file
0
lib/src/encryption/ed25519_utils.dart
Normal file
0
lib/src/encryption/message_seal.dart
Normal file
0
lib/src/encryption/message_seal.dart
Normal file
0
lib/src/models/peer.dart
Normal file
0
lib/src/models/peer.dart
Normal file
22
lib/src/models/queued_message.dart
Normal file
22
lib/src/models/queued_message.dart
Normal file
|
@ -0,0 +1,22 @@
|
|||
class QueuedMessage {
|
||||
final String recipientId;
|
||||
final String message;
|
||||
final DateTime timestamp;
|
||||
final int retryCount;
|
||||
|
||||
QueuedMessage({
|
||||
required this.recipientId,
|
||||
required this.message,
|
||||
DateTime? timestamp,
|
||||
this.retryCount = 0,
|
||||
}) : timestamp = timestamp ?? DateTime.now();
|
||||
|
||||
QueuedMessage copyWith({int? retryCount}) {
|
||||
return QueuedMessage(
|
||||
recipientId: recipientId,
|
||||
message: message,
|
||||
timestamp: timestamp,
|
||||
retryCount: retryCount ?? this.retryCount,
|
||||
);
|
||||
}
|
||||
}
|
0
lib/src/models/user.dart
Normal file
0
lib/src/models/user.dart
Normal file
0
lib/src/p2p/grpc/client/peer_client.dart
Normal file
0
lib/src/p2p/grpc/client/peer_client.dart
Normal file
36
lib/src/p2p/grpc/server/grpc_server.dart
Normal file
36
lib/src/p2p/grpc/server/grpc_server.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'dart:io';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:torchat/src/p2p/message_queue/outgoing_queue.dart';
|
||||
|
||||
class TorChatGrpcServer extends ChatServiceBase {
|
||||
final OutgoingQueue queue;
|
||||
|
||||
TorChatGrpcServer(this.queue);
|
||||
|
||||
// RPC: SendMessage
|
||||
@override
|
||||
Future<ChatMessageReply> sendMessage(
|
||||
ServiceCall call, ChatMessageRequest req) async {
|
||||
// Simply enqueue for now
|
||||
queue.enqueue(req.message);
|
||||
return ChatMessageReply(success: true, messageId: req.message.id);
|
||||
}
|
||||
|
||||
// TODO: implement StreamMessages, FetchHistory, etc.
|
||||
}
|
||||
|
||||
class GrpcServerRunner {
|
||||
Server? _server;
|
||||
|
||||
Future<void> start(OutgoingQueue queue, {int port = 20900}) async {
|
||||
_server = Server(
|
||||
[TorChatGrpcServer(queue)],
|
||||
const <Interceptor>[],
|
||||
CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]),
|
||||
);
|
||||
await _server!.serve(port: port);
|
||||
stdout.writeln('gRPC server listening on port $port');
|
||||
}
|
||||
|
||||
Future<void> stop() => _server?.shutdown() ?? Future.value();
|
||||
}
|
0
lib/src/p2p/message_queue/inbound_handler.dart
Normal file
0
lib/src/p2p/message_queue/inbound_handler.dart
Normal file
14
lib/src/p2p/message_queue/outgoing_queue.dart
Normal file
14
lib/src/p2p/message_queue/outgoing_queue.dart
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
|
||||
class OutgoingQueue {
|
||||
final _list = <Message>[];
|
||||
|
||||
void enqueue(Message msg) => _list.add(msg);
|
||||
bool get hasMessages => _list.isNotEmpty;
|
||||
|
||||
List<Message> drain([int max = 50]) {
|
||||
final drain = _list.take(max).toList();
|
||||
_list.removeRange(0, drain.length);
|
||||
return drain;
|
||||
}
|
||||
}
|
26
lib/src/p2p/sync_manager.dart
Normal file
26
lib/src/p2p/sync_manager.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:torchat/src/p2p/message_queue/outgoing_queue.dart';
|
||||
|
||||
class SyncManager {
|
||||
final OutgoingQueue queue;
|
||||
final Duration interval;
|
||||
Timer? _timer;
|
||||
|
||||
SyncManager(this.queue, {this.interval = const Duration(seconds: 10)});
|
||||
|
||||
void start() {
|
||||
_timer ??= Timer.periodic(interval, (_) => _flush());
|
||||
}
|
||||
|
||||
void stop() => _timer?.cancel();
|
||||
|
||||
Future<void> _flush() async {
|
||||
if (!queue.hasMessages) return;
|
||||
final msgs = queue.drain();
|
||||
// TODO: iterate peers from discovery service and send via gRPC client
|
||||
for (final msg in msgs) {
|
||||
print('[SyncManager] Would send message ${msg.id}');
|
||||
}
|
||||
}
|
||||
}
|
0
lib/src/p2p/tor/tor_service.dart
Normal file
0
lib/src/p2p/tor/tor_service.dart
Normal file
0
lib/src/pow/pow_verifier.dart
Normal file
0
lib/src/pow/pow_verifier.dart
Normal file
0
lib/src/relay.dart
Normal file
0
lib/src/relay.dart
Normal file
0
lib/src/repository/group_repository.dart
Normal file
0
lib/src/repository/group_repository.dart
Normal file
0
lib/src/repository/message_repository.dart
Normal file
0
lib/src/repository/message_repository.dart
Normal file
0
lib/src/repository/user_repository.dart
Normal file
0
lib/src/repository/user_repository.dart
Normal file
0
lib/src/storage/spool.dart
Normal file
0
lib/src/storage/spool.dart
Normal file
0
lib/src/storage/sqlite/migrations/v1.sql
Normal file
0
lib/src/storage/sqlite/migrations/v1.sql
Normal file
3
lib/torchat.dart
Normal file
3
lib/torchat.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
int calculate() {
|
||||
return 6 * 7;
|
||||
}
|
75
protospec/chat.proto
Normal file
75
protospec/chat.proto
Normal file
|
@ -0,0 +1,75 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package torchat;
|
||||
|
||||
import "user.proto";
|
||||
|
||||
option java_package = "org.torchat.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message GroupId {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message GroupProfile {
|
||||
GroupId group_id = 1;
|
||||
string display_name = 2;
|
||||
string description = 3;
|
||||
repeated user.UserId members = 4;
|
||||
user.UserId creator = 5;
|
||||
int64 created_at = 6;
|
||||
string avatar_url = 7;
|
||||
repeated string tags = 8;
|
||||
string rules = 9;
|
||||
}
|
||||
|
||||
message GroupMember {
|
||||
user.UserId user_id = 1;
|
||||
bool is_admin = 2;
|
||||
bool is_muted = 3;
|
||||
int64 joined_at = 4;
|
||||
}
|
||||
|
||||
message GroupMessage {
|
||||
GroupId group_id = 1;
|
||||
user.UserId sender = 2;
|
||||
string content = 3;
|
||||
int64 timestamp = 4;
|
||||
string message_id = 5;
|
||||
}
|
||||
|
||||
message CreateGroupRequest {
|
||||
GroupProfile profile = 1;
|
||||
}
|
||||
|
||||
message CreateGroupReply {
|
||||
GroupId group_id = 1;
|
||||
}
|
||||
|
||||
message GetGroupRequest {
|
||||
GroupId group_id = 1;
|
||||
}
|
||||
|
||||
message GetGroupReply {
|
||||
GroupProfile profile = 1;
|
||||
repeated GroupMember members = 2;
|
||||
}
|
||||
|
||||
message SendGroupMessageRequest {
|
||||
GroupMessage message = 1;
|
||||
}
|
||||
|
||||
message SendGroupMessageReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message StreamGroupMessagesRequest {
|
||||
GroupId group_id = 1;
|
||||
}
|
||||
|
||||
service GroupService {
|
||||
rpc CreateGroup(CreateGroupRequest) returns (CreateGroupReply);
|
||||
rpc GetGroup(GetGroupRequest) returns (GetGroupReply);
|
||||
rpc SendMessage(SendGroupMessageRequest) returns (SendGroupMessageReply);
|
||||
rpc StreamMessages(StreamGroupMessagesRequest) returns (stream GroupMessage);
|
||||
}
|
75
protospec/group.proto
Normal file
75
protospec/group.proto
Normal file
|
@ -0,0 +1,75 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package torchat;
|
||||
|
||||
import "user.proto";
|
||||
|
||||
option java_package = "org.torchat.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message GroupId {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message GroupProfile {
|
||||
GroupId group_id = 1;
|
||||
string display_name = 2;
|
||||
string description = 3;
|
||||
repeated user.UserId members = 4;
|
||||
user.UserId creator = 5;
|
||||
int64 created_at = 6;
|
||||
string avatar_url = 7;
|
||||
repeated string tags = 8;
|
||||
string rules = 9;
|
||||
}
|
||||
|
||||
message GroupMember {
|
||||
user.UserId user_id = 1;
|
||||
bool is_admin = 2;
|
||||
bool is_muted = 3;
|
||||
int64 joined_at = 4;
|
||||
}
|
||||
|
||||
message GroupMessage {
|
||||
GroupId group_id = 1;
|
||||
user.UserId sender = 2;
|
||||
string content = 3;
|
||||
int64 timestamp = 4;
|
||||
string message_id = 5;
|
||||
}
|
||||
|
||||
message CreateGroupRequest {
|
||||
GroupProfile profile = 1;
|
||||
}
|
||||
|
||||
message CreateGroupReply {
|
||||
GroupId group_id = 1;
|
||||
}
|
||||
|
||||
message GetGroupRequest {
|
||||
GroupId group_id = 1;
|
||||
}
|
||||
|
||||
message GetGroupReply {
|
||||
GroupProfile profile = 1;
|
||||
repeated GroupMember members = 2;
|
||||
}
|
||||
|
||||
message SendGroupMessageRequest {
|
||||
GroupMessage message = 1;
|
||||
}
|
||||
|
||||
message SendGroupMessageReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message StreamGroupMessagesRequest {
|
||||
GroupId group_id = 1;
|
||||
}
|
||||
|
||||
service GroupService {
|
||||
rpc CreateGroup(CreateGroupRequest) returns (CreateGroupReply);
|
||||
rpc GetGroup(GetGroupRequest) returns (GetGroupReply);
|
||||
rpc SendMessage(SendGroupMessageRequest) returns (SendGroupMessageReply);
|
||||
rpc StreamMessages(StreamGroupMessagesRequest) returns (stream GroupMessage);
|
||||
}
|
100
protospec/user.proto
Normal file
100
protospec/user.proto
Normal file
|
@ -0,0 +1,100 @@
|
|||
syntax = "proto3";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TorChat User Identity & Reputation Spec (v0.1)
|
||||
// ---------------------------------------------------------------------------
|
||||
// * A user is a long-term Ed25519 key-pair.
|
||||
// * Profile fields are optional and fully signed.
|
||||
// * Multi-device keys are supported via DeviceKey.
|
||||
// * Third-party attestations ("signatures") build a web-of-trust layer.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
package torchat.identity;
|
||||
|
||||
option go_package = "github.com/torchat/proto/identity";
|
||||
option java_package = "org.torchat.identity";
|
||||
option java_multiple_files = true;
|
||||
|
||||
// -------------------------------------------------
|
||||
// Primitive types
|
||||
// -------------------------------------------------
|
||||
message PublicKey { bytes value = 1; } // Ed25519 32-byte
|
||||
message Signature { bytes value = 1; } // Ed25519 64-byte on SHA-256 hash
|
||||
message Sha256Hash { bytes value = 1; } // 32-byte hash
|
||||
|
||||
// -------------------------------------------------
|
||||
// User Profile (self-signed)
|
||||
// -------------------------------------------------
|
||||
message UserProfile {
|
||||
PublicKey pubkey = 1; // unique master key
|
||||
string nickname = 2; // optional alias
|
||||
string bio = 3; // optional free-text
|
||||
string avatar_url = 4; // optional (ipfs:// or https://)
|
||||
int64 created_at = 5; // epoch millis
|
||||
uint32 version = 6; // profile schema version
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Device key (per-device subkey, signed by master)
|
||||
// -------------------------------------------------
|
||||
message DeviceKey {
|
||||
string device_id = 1; // random UUID / human name
|
||||
PublicKey device_pk = 2; // Ed25519 key for this device
|
||||
int64 created_at = 3;
|
||||
Signature master_sig = 4; // master key sig over hash(device)
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Third-party attestation (web-of-trust)
|
||||
// -------------------------------------------------
|
||||
message Attestation {
|
||||
enum Purpose {
|
||||
GENERIC_TRUST = 0; // default "I trust this user"
|
||||
MODERATOR_ROLE = 1;
|
||||
NOTARY_ROLE = 2; // can co-sign trades, escrow etc.
|
||||
}
|
||||
|
||||
PublicKey signer_pk = 1; // who signs (must be known peer)
|
||||
bytes subject_pk = 2; // user being attested
|
||||
Purpose purpose = 3;
|
||||
string memo = 4; // free text or JSON
|
||||
int64 timestamp = 5;
|
||||
Signature signature = 6; // sig(signer) over hash(all above fields)
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Full signed user record
|
||||
// -------------------------------------------------
|
||||
message SignedUser {
|
||||
UserProfile profile = 1; // MUST contain self-sig
|
||||
Signature self_sig = 2; // sig(master) over hash(profile)
|
||||
repeated DeviceKey devices = 3; // 0+ devices
|
||||
repeated Attestation attestations = 4; // 0+ third-party sigs
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// gRPC Identity Service
|
||||
// -------------------------------------------------
|
||||
service IdentityService {
|
||||
// Register or update your profile. Must include self_sig.
|
||||
rpc RegisterUser (SignedUser) returns (Ack);
|
||||
|
||||
// Add or revoke a device key (master-signed).
|
||||
rpc UpsertDeviceKey (DeviceKey) returns (Ack);
|
||||
|
||||
// Add a third-party attestation.
|
||||
rpc AddAttestation (Attestation) returns (Ack);
|
||||
|
||||
// Get full user record.
|
||||
rpc GetUser (PublicKey) returns (SignedUser);
|
||||
|
||||
// Stream attestations about a user.
|
||||
rpc StreamAttestations(PublicKey) returns (stream Attestation);
|
||||
}
|
||||
|
||||
// Generic acknowledge wrapper
|
||||
message Ack {
|
||||
enum Status { OK = 0; ERROR = 1; }
|
||||
Status status = 1;
|
||||
string message = 2; // optional error / info
|
||||
}
|
21
pubspec.yaml
Normal file
21
pubspec.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: torchat
|
||||
description: A gRPC-based anonymous chat node (client / server) built with Dart and Tor/Arti
|
||||
version: 0.1.0
|
||||
homepage: https://foss.haveno.com/tor-project/torchat
|
||||
environment:
|
||||
sdk: '>=3.4.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
grpc: ^3.2.4
|
||||
protobuf: ^3.1.0
|
||||
convert: ^3.1.1
|
||||
logging: ^1.2.0
|
||||
args: ^2.5.0
|
||||
yaml: ^3.1.2
|
||||
path: ^1.9.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^3.0.0
|
||||
test: ^1.25.0
|
||||
build_runner: ^2.4.7
|
||||
protoc_plugin: ^21.1.2
|
8
test/torchat_test.dart
Normal file
8
test/torchat_test.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
import 'package:torchat/torchat.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('calculate', () {
|
||||
expect(calculate(), 42);
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue