import 'dart:async'; import 'dart:convert'; import 'package:arbiter/features/connection/connection.dart'; import 'package:arbiter/features/connection/server_info_storage.dart'; import 'package:arbiter/features/identity/pk_manager.dart'; import 'package:arbiter/proto/arbiter.pbgrpc.dart'; import 'package:arbiter/proto/user_agent.pb.dart'; import 'package:grpc/grpc.dart'; import 'package:mtcore/markettakers.dart'; Future connectAndAuthorize( StoredServerInfo serverInfo, KeyHandle key, { String? bootstrapToken, }) async { try { final connection = await _connect(serverInfo); talker.info( 'Connected to server at ${serverInfo.address}:${serverInfo.port}', ); final pubkey = await key.getPublicKey(); final req = AuthChallengeRequest( pubkey: pubkey, bootstrapToken: bootstrapToken, keyType: switch (key.alg) { KeyAlgorithm.rsa => KeyType.KEY_TYPE_RSA, KeyAlgorithm.ecdsa => KeyType.KEY_TYPE_ECDSA_SECP256K1, KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519, }, ); await connection.send(UserAgentRequest(authChallengeRequest: req)); talker.info( "Sent auth challenge request with pubkey ${base64Encode(pubkey)}", ); final response = await connection.receive(); talker.info('Received response from server, checking auth flow...'); if (response.hasAuthOk()) { talker.info('Authentication successful, connection established'); return connection; } if (!response.hasAuthChallenge()) { throw Exception( 'Expected AuthChallengeResponse, got ${response.whichPayload()}', ); } final challenge = _formatChallenge(response.authChallenge, pubkey); talker.info( 'Received auth challenge, signing with key ${base64Encode(pubkey)}', ); final signature = await key.sign(challenge); await connection.send( UserAgentRequest(authChallengeSolution: AuthChallengeSolution(signature: signature)), ); talker.info('Sent auth challenge solution, waiting for server response...'); final solutionResponse = await connection.receive(); if (!solutionResponse.hasAuthOk()) { throw Exception( 'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}', ); } talker.info('Authentication successful, connection established'); return connection; } catch (e) { throw Exception('Failed to connect to server: $e'); } } Future _connect(StoredServerInfo serverInfo) async { final channel = ClientChannel( serverInfo.address, port: serverInfo.port, options: ChannelOptions( connectTimeout: const Duration(seconds: 10), credentials: ChannelCredentials.secure( onBadCertificate: (cert, host) { return true; }, ), ), ); final client = ArbiterServiceClient(channel); final tx = StreamController(); final rx = client.userAgent(tx.stream); return Connection(channel: channel, tx: tx, rx: rx); } List _formatChallenge(AuthChallenge challenge, List pubkey) { final encodedPubkey = base64Encode(pubkey); final payload = "${challenge.nonce}:$encodedPubkey"; return utf8.encode(payload); }