general idea and structure together

This commit is contained in:
Kewbit 2025-06-24 00:53:53 +07:00
parent d0dae05e6e
commit c0c744ab35
31 changed files with 445 additions and 0 deletions

3
CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

30
analysis_options.yaml Normal file
View 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
View 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.');
}

View file

View file

View file

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

View file

View file

0
lib/src/models/peer.dart Normal file
View file

View 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
View file

View file

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

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

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

View file

View file

0
lib/src/relay.dart Normal file
View file

View file

View file

View file

View file

3
lib/torchat.dart Normal file
View file

@ -0,0 +1,3 @@
int calculate() {
return 6 * 7;
}

75
protospec/chat.proto Normal file
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,8 @@
import 'package:torchat/torchat.dart';
import 'package:test/test.dart';
void main() {
test('calculate', () {
expect(calculate(), 42);
});
}