fix(clippy): apply auto-fixable linting suggestions

This commit is contained in:
Clippy Bot
2026-06-23 18:47:23 +00:00
parent 4fd75701c7
commit cc21036448
425 changed files with 79190 additions and 79190 deletions

View File

@@ -1,58 +1,58 @@
import 'dart:convert';
class ArbiterUrl {
const ArbiterUrl({
required this.host,
required this.port,
required this.caCert,
this.bootstrapToken,
});
final String host;
final int port;
final List<int> caCert;
final String? bootstrapToken;
static const _scheme = 'arbiter';
static const _certQueryKey = 'cert';
static const _bootstrapTokenQueryKey = 'bootstrap_token';
static ArbiterUrl parse(String value) {
final uri = Uri.tryParse(value);
if (uri == null || uri.scheme != _scheme) {
throw const FormatException("Invalid URL scheme, expected 'arbiter://'");
}
if (uri.host.isEmpty) {
throw const FormatException('Missing host in URL');
}
if (!uri.hasPort) {
throw const FormatException('Missing port in URL');
}
final cert = uri.queryParameters[_certQueryKey];
if (cert == null || cert.isEmpty) {
throw const FormatException("Missing 'cert' query parameter in URL");
}
final decodedCert = _decodeCert(cert);
return ArbiterUrl(
host: uri.host,
port: uri.port,
caCert: decodedCert,
bootstrapToken: uri.queryParameters[_bootstrapTokenQueryKey],
);
}
static List<int> _decodeCert(String cert) {
try {
return base64Url.decode(base64Url.normalize(cert));
} on FormatException catch (error) {
throw FormatException(
"Invalid base64 in 'cert' query parameter: ${error.message}",
);
}
}
}
import 'dart:convert';
class ArbiterUrl {
const ArbiterUrl({
required this.host,
required this.port,
required this.caCert,
this.bootstrapToken,
});
final String host;
final int port;
final List<int> caCert;
final String? bootstrapToken;
static const _scheme = 'arbiter';
static const _certQueryKey = 'cert';
static const _bootstrapTokenQueryKey = 'bootstrap_token';
static ArbiterUrl parse(String value) {
final uri = Uri.tryParse(value);
if (uri == null || uri.scheme != _scheme) {
throw const FormatException("Invalid URL scheme, expected 'arbiter://'");
}
if (uri.host.isEmpty) {
throw const FormatException('Missing host in URL');
}
if (!uri.hasPort) {
throw const FormatException('Missing port in URL');
}
final cert = uri.queryParameters[_certQueryKey];
if (cert == null || cert.isEmpty) {
throw const FormatException("Missing 'cert' query parameter in URL");
}
final decodedCert = _decodeCert(cert);
return ArbiterUrl(
host: uri.host,
port: uri.port,
caCert: decodedCert,
bootstrapToken: uri.queryParameters[_bootstrapTokenQueryKey],
);
}
static List<int> _decodeCert(String cert) {
try {
return base64Url.decode(base64Url.normalize(cert));
} on FormatException catch (error) {
throw FormatException(
"Invalid base64 in 'cert' query parameter: ${error.message}",
);
}
}
}

View File

@@ -1,170 +1,170 @@
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/auth.pb.dart' as ua_auth;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:arbiter/src/rust/api.dart';
import 'package:grpc/grpc.dart';
import 'package:mtcore/markettakers.dart';
class AuthorizationException implements Exception {
const AuthorizationException(this.result);
final ua_auth.AuthResult result;
String get message => switch (result) {
ua_auth.AuthResult.AUTH_RESULT_INVALID_KEY =>
'Authentication failed: this device key is not registered on the server.',
ua_auth.AuthResult.AUTH_RESULT_INVALID_SIGNATURE =>
'Authentication failed: the server rejected the signature for this device key.',
ua_auth.AuthResult.AUTH_RESULT_BOOTSTRAP_REQUIRED =>
'Authentication failed: the server requires bootstrap before this device can connect.',
ua_auth.AuthResult.AUTH_RESULT_TOKEN_INVALID =>
'Authentication failed: the bootstrap token is invalid.',
ua_auth.AuthResult.AUTH_RESULT_INTERNAL =>
'Authentication failed: the server hit an internal error.',
ua_auth.AuthResult.AUTH_RESULT_UNSPECIFIED =>
'Authentication failed: the server returned an unspecified auth error.',
ua_auth.AuthResult.AUTH_RESULT_SUCCESS => 'Authentication succeeded.',
_ => 'Authentication failed: ${result.name}.',
};
@override
String toString() => message;
}
class ConnectionException implements Exception {
const ConnectionException(this.message);
final String message;
@override
String toString() => message;
}
Future<Connection> connectAndAuthorize(
StoredServerInfo serverInfo,
KeyHandle key, {
String? bootstrapToken,
}) async {
Connection? connection;
try {
connection = await _connect(serverInfo);
talker.info(
'Connected to server at ${serverInfo.address}:${serverInfo.port}',
);
final pubkey = await key.getPublicKey();
final req = ua_auth.AuthChallengeRequest(
pubkey: pubkey,
bootstrapToken: bootstrapToken,
);
final response = await connection.ask(
UserAgentRequest(auth: ua_auth.Request(challengeRequest: req)),
);
talker.info(
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
);
talker.info('Received response from server, checking auth flow...');
if (!response.hasAuth()) {
throw ConnectionException(
'Expected auth response, got ${response.whichPayload()}',
);
}
final authResponse = response.auth;
if (authResponse.hasResult()) {
if (authResponse.result != ua_auth.AuthResult.AUTH_RESULT_SUCCESS) {
throw AuthorizationException(authResponse.result);
}
talker.info('Authentication successful, connection established');
return connection;
}
if (!authResponse.hasChallenge()) {
throw ConnectionException(
'Expected auth challenge response, got ${authResponse.whichPayload()}',
);
}
final challenge = await formatChallenge(
random: authResponse.challenge.random,
timestamp: authResponse.challenge.timestampNanos.toInt(),
);
talker.info(
'Received auth challenge, signing with key ${base64Encode(pubkey)}',
);
final signature = await key.sign(challenge);
final solutionResponse = await connection.ask(
UserAgentRequest(
auth: ua_auth.Request(
challengeSolution: ua_auth.AuthChallengeSolution(
signature: signature,
),
),
),
);
talker.info('Sent auth challenge solution, waiting for server response...');
if (!solutionResponse.hasAuth()) {
throw ConnectionException(
'Expected auth solution response, got ${solutionResponse.whichPayload()}',
);
}
final authSolutionResponse = solutionResponse.auth;
if (!authSolutionResponse.hasResult()) {
throw ConnectionException(
'Expected auth solution result, got ${authSolutionResponse.whichPayload()}',
);
}
if (authSolutionResponse.result != ua_auth.AuthResult.AUTH_RESULT_SUCCESS) {
throw AuthorizationException(authSolutionResponse.result);
}
talker.info('Authentication successful, connection established');
return connection;
} on AuthorizationException {
await connection?.close();
rethrow;
} on GrpcError catch (error) {
await connection?.close();
throw ConnectionException('Failed to connect to server: ${error.message}');
} catch (e) {
await connection?.close();
if (e is ConnectionException) {
rethrow;
}
throw ConnectionException('Failed to connect to server: $e');
}
}
Future<Connection> _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<UserAgentRequest>();
final rx = client.userAgent(tx.stream);
return Connection(channel: channel, tx: tx, rx: rx);
}
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/auth.pb.dart' as ua_auth;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:arbiter/src/rust/api.dart';
import 'package:grpc/grpc.dart';
import 'package:mtcore/markettakers.dart';
class AuthorizationException implements Exception {
const AuthorizationException(this.result);
final ua_auth.AuthResult result;
String get message => switch (result) {
ua_auth.AuthResult.AUTH_RESULT_INVALID_KEY =>
'Authentication failed: this device key is not registered on the server.',
ua_auth.AuthResult.AUTH_RESULT_INVALID_SIGNATURE =>
'Authentication failed: the server rejected the signature for this device key.',
ua_auth.AuthResult.AUTH_RESULT_BOOTSTRAP_REQUIRED =>
'Authentication failed: the server requires bootstrap before this device can connect.',
ua_auth.AuthResult.AUTH_RESULT_TOKEN_INVALID =>
'Authentication failed: the bootstrap token is invalid.',
ua_auth.AuthResult.AUTH_RESULT_INTERNAL =>
'Authentication failed: the server hit an internal error.',
ua_auth.AuthResult.AUTH_RESULT_UNSPECIFIED =>
'Authentication failed: the server returned an unspecified auth error.',
ua_auth.AuthResult.AUTH_RESULT_SUCCESS => 'Authentication succeeded.',
_ => 'Authentication failed: ${result.name}.',
};
@override
String toString() => message;
}
class ConnectionException implements Exception {
const ConnectionException(this.message);
final String message;
@override
String toString() => message;
}
Future<Connection> connectAndAuthorize(
StoredServerInfo serverInfo,
KeyHandle key, {
String? bootstrapToken,
}) async {
Connection? connection;
try {
connection = await _connect(serverInfo);
talker.info(
'Connected to server at ${serverInfo.address}:${serverInfo.port}',
);
final pubkey = await key.getPublicKey();
final req = ua_auth.AuthChallengeRequest(
pubkey: pubkey,
bootstrapToken: bootstrapToken,
);
final response = await connection.ask(
UserAgentRequest(auth: ua_auth.Request(challengeRequest: req)),
);
talker.info(
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
);
talker.info('Received response from server, checking auth flow...');
if (!response.hasAuth()) {
throw ConnectionException(
'Expected auth response, got ${response.whichPayload()}',
);
}
final authResponse = response.auth;
if (authResponse.hasResult()) {
if (authResponse.result != ua_auth.AuthResult.AUTH_RESULT_SUCCESS) {
throw AuthorizationException(authResponse.result);
}
talker.info('Authentication successful, connection established');
return connection;
}
if (!authResponse.hasChallenge()) {
throw ConnectionException(
'Expected auth challenge response, got ${authResponse.whichPayload()}',
);
}
final challenge = await formatChallenge(
random: authResponse.challenge.random,
timestamp: authResponse.challenge.timestampNanos.toInt(),
);
talker.info(
'Received auth challenge, signing with key ${base64Encode(pubkey)}',
);
final signature = await key.sign(challenge);
final solutionResponse = await connection.ask(
UserAgentRequest(
auth: ua_auth.Request(
challengeSolution: ua_auth.AuthChallengeSolution(
signature: signature,
),
),
),
);
talker.info('Sent auth challenge solution, waiting for server response...');
if (!solutionResponse.hasAuth()) {
throw ConnectionException(
'Expected auth solution response, got ${solutionResponse.whichPayload()}',
);
}
final authSolutionResponse = solutionResponse.auth;
if (!authSolutionResponse.hasResult()) {
throw ConnectionException(
'Expected auth solution result, got ${authSolutionResponse.whichPayload()}',
);
}
if (authSolutionResponse.result != ua_auth.AuthResult.AUTH_RESULT_SUCCESS) {
throw AuthorizationException(authSolutionResponse.result);
}
talker.info('Authentication successful, connection established');
return connection;
} on AuthorizationException {
await connection?.close();
rethrow;
} on GrpcError catch (error) {
await connection?.close();
throw ConnectionException('Failed to connect to server: ${error.message}');
} catch (e) {
await connection?.close();
if (e is ConnectionException) {
rethrow;
}
throw ConnectionException('Failed to connect to server: $e');
}
}
Future<Connection> _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<UserAgentRequest>();
final rx = client.userAgent(tx.stream);
return Connection(channel: channel, tx: tx, rx: rx);
}

View File

@@ -1,136 +1,136 @@
import 'dart:async';
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:grpc/grpc.dart';
import 'package:mtcore/markettakers.dart';
class Connection {
Connection({
required this.channel,
required StreamController<UserAgentRequest> tx,
required ResponseStream<UserAgentResponse> rx,
}) : _tx = tx {
_rxSubscription = rx.listen(
_handleResponse,
onError: _handleError,
onDone: _handleDone,
cancelOnError: true,
);
}
final ClientChannel channel;
final StreamController<UserAgentRequest> _tx;
final Map<int, Completer<UserAgentResponse>> _pendingRequests = {};
final StreamController<UserAgentResponse> _outOfBandMessages =
StreamController<UserAgentResponse>.broadcast();
StreamSubscription<UserAgentResponse>? _rxSubscription;
int _nextRequestId = 0;
Stream<UserAgentResponse> get outOfBandMessages => _outOfBandMessages.stream;
Future<UserAgentResponse> ask(UserAgentRequest message) async {
_ensureOpen();
final requestId = _nextRequestId++;
final completer = Completer<UserAgentResponse>();
_pendingRequests[requestId] = completer;
message.id = requestId;
talker.debug('Sending request: ${message.toDebugString()}');
try {
_tx.add(message);
} catch (error, stackTrace) {
_pendingRequests.remove(requestId);
completer.completeError(error, stackTrace);
}
return completer.future;
}
Future<void> tell(UserAgentRequest message) async {
_ensureOpen();
final requestId = _nextRequestId++;
message.id = requestId;
talker.debug('Sending message: ${message.toDebugString()}');
try {
_tx.add(message);
} catch (error, stackTrace) {
talker.error('Failed to send message: $error', error, stackTrace);
}
}
Future<void> close() async {
talker.debug('Closing connection...');
final rxSubscription = _rxSubscription;
if (rxSubscription == null) {
return;
}
_rxSubscription = null;
await rxSubscription.cancel();
_failPendingRequests(Exception('Connection closed.'));
await _outOfBandMessages.close();
await _tx.close();
await channel.shutdown();
}
void _handleResponse(UserAgentResponse response) {
talker.debug('Received response: ${response.toDebugString()}');
if (response.hasId()) {
final completer = _pendingRequests.remove(response.id);
if (completer == null) {
talker.warning(
'Received response for unknown request id ${response.id}',
);
return;
}
completer.complete(response);
return;
}
_outOfBandMessages.add(response);
}
void _handleError(Object error, StackTrace stackTrace) {
_rxSubscription = null;
_failPendingRequests(error, stackTrace);
_outOfBandMessages.addError(error, stackTrace);
}
void _handleDone() {
talker.debug('Connection closed by server.');
if (_rxSubscription == null) {
return;
}
_rxSubscription = null;
final error = Exception(
'Connection closed while waiting for server response.',
);
_failPendingRequests(error);
_outOfBandMessages.close();
}
void _failPendingRequests(Object error, [StackTrace? stackTrace]) {
final pendingRequests = _pendingRequests.values.toList(growable: false);
_pendingRequests.clear();
for (final completer in pendingRequests) {
if (!completer.isCompleted) {
completer.completeError(error, stackTrace);
}
}
}
void _ensureOpen() {
if (_rxSubscription == null) {
throw StateError('Connection is closed.');
}
}
}
import 'dart:async';
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:grpc/grpc.dart';
import 'package:mtcore/markettakers.dart';
class Connection {
Connection({
required this.channel,
required StreamController<UserAgentRequest> tx,
required ResponseStream<UserAgentResponse> rx,
}) : _tx = tx {
_rxSubscription = rx.listen(
_handleResponse,
onError: _handleError,
onDone: _handleDone,
cancelOnError: true,
);
}
final ClientChannel channel;
final StreamController<UserAgentRequest> _tx;
final Map<int, Completer<UserAgentResponse>> _pendingRequests = {};
final StreamController<UserAgentResponse> _outOfBandMessages =
StreamController<UserAgentResponse>.broadcast();
StreamSubscription<UserAgentResponse>? _rxSubscription;
int _nextRequestId = 0;
Stream<UserAgentResponse> get outOfBandMessages => _outOfBandMessages.stream;
Future<UserAgentResponse> ask(UserAgentRequest message) async {
_ensureOpen();
final requestId = _nextRequestId++;
final completer = Completer<UserAgentResponse>();
_pendingRequests[requestId] = completer;
message.id = requestId;
talker.debug('Sending request: ${message.toDebugString()}');
try {
_tx.add(message);
} catch (error, stackTrace) {
_pendingRequests.remove(requestId);
completer.completeError(error, stackTrace);
}
return completer.future;
}
Future<void> tell(UserAgentRequest message) async {
_ensureOpen();
final requestId = _nextRequestId++;
message.id = requestId;
talker.debug('Sending message: ${message.toDebugString()}');
try {
_tx.add(message);
} catch (error, stackTrace) {
talker.error('Failed to send message: $error', error, stackTrace);
}
}
Future<void> close() async {
talker.debug('Closing connection...');
final rxSubscription = _rxSubscription;
if (rxSubscription == null) {
return;
}
_rxSubscription = null;
await rxSubscription.cancel();
_failPendingRequests(Exception('Connection closed.'));
await _outOfBandMessages.close();
await _tx.close();
await channel.shutdown();
}
void _handleResponse(UserAgentResponse response) {
talker.debug('Received response: ${response.toDebugString()}');
if (response.hasId()) {
final completer = _pendingRequests.remove(response.id);
if (completer == null) {
talker.warning(
'Received response for unknown request id ${response.id}',
);
return;
}
completer.complete(response);
return;
}
_outOfBandMessages.add(response);
}
void _handleError(Object error, StackTrace stackTrace) {
_rxSubscription = null;
_failPendingRequests(error, stackTrace);
_outOfBandMessages.addError(error, stackTrace);
}
void _handleDone() {
talker.debug('Connection closed by server.');
if (_rxSubscription == null) {
return;
}
_rxSubscription = null;
final error = Exception(
'Connection closed while waiting for server response.',
);
_failPendingRequests(error);
_outOfBandMessages.close();
}
void _failPendingRequests(Object error, [StackTrace? stackTrace]) {
final pendingRequests = _pendingRequests.values.toList(growable: false);
_pendingRequests.clear();
for (final completer in pendingRequests) {
if (!completer.isCompleted) {
completer.completeError(error, stackTrace);
}
}
}
void _ensureOpen() {
if (_rxSubscription == null) {
throw StateError('Connection is closed.');
}
}
}

View File

@@ -1,67 +1,67 @@
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/proto/user_agent/evm.pb.dart' as ua_evm;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
final response = await connection.ask(
UserAgentRequest(evm: ua_evm.Request(walletList: Empty())),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasWalletList()) {
throw Exception(
'Expected EVM wallet list response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.walletList;
switch (result.whichResult()) {
case WalletListResponse_Result.wallets:
return result.wallets.wallets.toList(growable: false);
case WalletListResponse_Result.error:
throw Exception(_describeEvmError(result.error));
case WalletListResponse_Result.notSet:
throw Exception('EVM wallet list response was empty.');
}
}
Future<void> createEvmWallet(Connection connection) async {
final response = await connection.ask(
UserAgentRequest(evm: ua_evm.Request(walletCreate: Empty())),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasWalletCreate()) {
throw Exception(
'Expected EVM wallet create response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.walletCreate;
switch (result.whichResult()) {
case WalletCreateResponse_Result.wallet:
return;
case WalletCreateResponse_Result.error:
throw Exception(_describeEvmError(result.error));
case WalletCreateResponse_Result.notSet:
throw Exception('Wallet creation returned no result.');
}
}
String _describeEvmError(EvmError error) {
return switch (error) {
EvmError.EVM_ERROR_VAULT_SEALED =>
'The vault is sealed. Unseal it before using EVM wallets.',
EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED =>
'The server failed to process the EVM request.',
_ => 'The server failed to process the EVM request.',
};
}
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/proto/user_agent/evm.pb.dart' as ua_evm;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
final response = await connection.ask(
UserAgentRequest(evm: ua_evm.Request(walletList: Empty())),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasWalletList()) {
throw Exception(
'Expected EVM wallet list response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.walletList;
switch (result.whichResult()) {
case WalletListResponse_Result.wallets:
return result.wallets.wallets.toList(growable: false);
case WalletListResponse_Result.error:
throw Exception(_describeEvmError(result.error));
case WalletListResponse_Result.notSet:
throw Exception('EVM wallet list response was empty.');
}
}
Future<void> createEvmWallet(Connection connection) async {
final response = await connection.ask(
UserAgentRequest(evm: ua_evm.Request(walletCreate: Empty())),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasWalletCreate()) {
throw Exception(
'Expected EVM wallet create response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.walletCreate;
switch (result.whichResult()) {
case WalletCreateResponse_Result.wallet:
return;
case WalletCreateResponse_Result.error:
throw Exception(_describeEvmError(result.error));
case WalletCreateResponse_Result.notSet:
throw Exception('Wallet creation returned no result.');
}
}
String _describeEvmError(EvmError error) {
return switch (error) {
EvmError.EVM_ERROR_VAULT_SEALED =>
'The vault is sealed. Unseal it before using EVM wallets.',
EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED =>
'The server failed to process the EVM request.',
_ => 'The server failed to process the EVM request.',
};
}

View File

@@ -1,102 +1,102 @@
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/proto/user_agent/evm.pb.dart' as ua_evm;
import 'package:arbiter/proto/user_agent.pb.dart';
Future<List<GrantEntry>> listEvmGrants(Connection connection) async {
final request = EvmGrantListRequest();
final response = await connection.ask(
UserAgentRequest(evm: ua_evm.Request(grantList: request)),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasGrantList()) {
throw Exception(
'Expected EVM grant list response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.grantList;
switch (result.whichResult()) {
case EvmGrantListResponse_Result.grants:
return result.grants.grants.toList(growable: false);
case EvmGrantListResponse_Result.error:
throw Exception(_describeGrantError(result.error));
case EvmGrantListResponse_Result.notSet:
throw Exception('EVM grant list response was empty.');
}
}
Future<int> createEvmGrant(
Connection connection, {
required SharedSettings sharedSettings,
required SpecificGrant specific,
}) async {
final request = UserAgentRequest(
evm: ua_evm.Request(
grantCreate: EvmGrantCreateRequest(
shared: sharedSettings,
specific: specific,
),
),
);
final resp = await connection.ask(request);
if (!resp.hasEvm()) {
throw Exception('Expected EVM response, got ${resp.whichPayload()}');
}
final evmResponse = resp.evm;
if (!evmResponse.hasGrantCreate()) {
throw Exception(
'Expected EVM grant create response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.grantCreate;
return result.grantId;
}
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
final response = await connection.ask(
UserAgentRequest(
evm: ua_evm.Request(grantDelete: EvmGrantDeleteRequest(grantId: grantId)),
),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasGrantDelete()) {
throw Exception(
'Expected EVM grant delete response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.grantDelete;
switch (result.whichResult()) {
case EvmGrantDeleteResponse_Result.ok:
return;
case EvmGrantDeleteResponse_Result.error:
throw Exception(_describeGrantError(result.error));
case EvmGrantDeleteResponse_Result.notSet:
throw Exception('Grant revoke returned no result.');
}
}
String _describeGrantError(EvmError error) {
return switch (error) {
EvmError.EVM_ERROR_VAULT_SEALED =>
'The vault is sealed. Unseal it before using EVM grants.',
EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED =>
'The server failed to process the EVM grant request.',
_ => 'The server failed to process the EVM grant request.',
};
}
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/proto/user_agent/evm.pb.dart' as ua_evm;
import 'package:arbiter/proto/user_agent.pb.dart';
Future<List<GrantEntry>> listEvmGrants(Connection connection) async {
final request = EvmGrantListRequest();
final response = await connection.ask(
UserAgentRequest(evm: ua_evm.Request(grantList: request)),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasGrantList()) {
throw Exception(
'Expected EVM grant list response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.grantList;
switch (result.whichResult()) {
case EvmGrantListResponse_Result.grants:
return result.grants.grants.toList(growable: false);
case EvmGrantListResponse_Result.error:
throw Exception(_describeGrantError(result.error));
case EvmGrantListResponse_Result.notSet:
throw Exception('EVM grant list response was empty.');
}
}
Future<int> createEvmGrant(
Connection connection, {
required SharedSettings sharedSettings,
required SpecificGrant specific,
}) async {
final request = UserAgentRequest(
evm: ua_evm.Request(
grantCreate: EvmGrantCreateRequest(
shared: sharedSettings,
specific: specific,
),
),
);
final resp = await connection.ask(request);
if (!resp.hasEvm()) {
throw Exception('Expected EVM response, got ${resp.whichPayload()}');
}
final evmResponse = resp.evm;
if (!evmResponse.hasGrantCreate()) {
throw Exception(
'Expected EVM grant create response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.grantCreate;
return result.grantId;
}
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
final response = await connection.ask(
UserAgentRequest(
evm: ua_evm.Request(grantDelete: EvmGrantDeleteRequest(grantId: grantId)),
),
);
if (!response.hasEvm()) {
throw Exception('Expected EVM response, got ${response.whichPayload()}');
}
final evmResponse = response.evm;
if (!evmResponse.hasGrantDelete()) {
throw Exception(
'Expected EVM grant delete response, got ${evmResponse.whichPayload()}',
);
}
final result = evmResponse.grantDelete;
switch (result.whichResult()) {
case EvmGrantDeleteResponse_Result.ok:
return;
case EvmGrantDeleteResponse_Result.error:
throw Exception(_describeGrantError(result.error));
case EvmGrantDeleteResponse_Result.notSet:
throw Exception('Grant revoke returned no result.');
}
}
String _describeGrantError(EvmError error) {
return switch (error) {
EvmError.EVM_ERROR_VAULT_SEALED =>
'The vault is sealed. Unseal it before using EVM grants.',
EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED =>
'The server failed to process the EVM grant request.',
_ => 'The server failed to process the EVM grant request.',
};
}

View File

@@ -1,86 +1,86 @@
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/user_agent/sdk_client.pb.dart' as ua_sdk;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
Future<Set<int>> readClientWalletAccess(
Connection connection, {
required int clientId,
}) async {
final response = await connection.ask(
UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())),
);
if (!response.hasSdkClient()) {
throw Exception(
'Expected SDK client response, got ${response.whichPayload()}',
);
}
final sdkClientResponse = response.sdkClient;
if (!sdkClientResponse.hasListWalletAccess()) {
throw Exception(
'Expected list wallet access response, got ${sdkClientResponse.whichPayload()}',
);
}
return {
for (final entry in sdkClientResponse.listWalletAccess.accesses)
if (entry.access.sdkClientId == clientId) entry.access.walletId,
};
}
Future<List<ua_sdk.WalletAccessEntry>> listAllWalletAccesses(
Connection connection,
) async {
final response = await connection.ask(
UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())),
);
if (!response.hasSdkClient()) {
throw Exception(
'Expected SDK client response, got ${response.whichPayload()}',
);
}
final sdkClientResponse = response.sdkClient;
if (!sdkClientResponse.hasListWalletAccess()) {
throw Exception(
'Expected list wallet access response, got ${sdkClientResponse.whichPayload()}',
);
}
return sdkClientResponse.listWalletAccess.accesses.toList(growable: false);
}
Future<void> writeClientWalletAccess(
Connection connection, {
required int clientId,
required Set<int> walletIds,
}) async {
final current = await readClientWalletAccess(connection, clientId: clientId);
final toGrant = walletIds.difference(current);
final toRevoke = current.difference(walletIds);
if (toGrant.isNotEmpty) {
await connection.tell(
UserAgentRequest(
sdkClient: ua_sdk.Request(
grantWalletAccess: ua_sdk.GrantWalletAccess(
accesses: [
for (final walletId in toGrant)
ua_sdk.WalletAccess(sdkClientId: clientId, walletId: walletId),
],
),
),
),
);
}
if (toRevoke.isNotEmpty) {
await connection.tell(
UserAgentRequest(
sdkClient: ua_sdk.Request(
revokeWalletAccess: ua_sdk.RevokeWalletAccess(
accesses: [for (final walletId in toRevoke) walletId],
),
),
),
);
}
}
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/user_agent/sdk_client.pb.dart' as ua_sdk;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
Future<Set<int>> readClientWalletAccess(
Connection connection, {
required int clientId,
}) async {
final response = await connection.ask(
UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())),
);
if (!response.hasSdkClient()) {
throw Exception(
'Expected SDK client response, got ${response.whichPayload()}',
);
}
final sdkClientResponse = response.sdkClient;
if (!sdkClientResponse.hasListWalletAccess()) {
throw Exception(
'Expected list wallet access response, got ${sdkClientResponse.whichPayload()}',
);
}
return {
for (final entry in sdkClientResponse.listWalletAccess.accesses)
if (entry.access.sdkClientId == clientId) entry.access.walletId,
};
}
Future<List<ua_sdk.WalletAccessEntry>> listAllWalletAccesses(
Connection connection,
) async {
final response = await connection.ask(
UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())),
);
if (!response.hasSdkClient()) {
throw Exception(
'Expected SDK client response, got ${response.whichPayload()}',
);
}
final sdkClientResponse = response.sdkClient;
if (!sdkClientResponse.hasListWalletAccess()) {
throw Exception(
'Expected list wallet access response, got ${sdkClientResponse.whichPayload()}',
);
}
return sdkClientResponse.listWalletAccess.accesses.toList(growable: false);
}
Future<void> writeClientWalletAccess(
Connection connection, {
required int clientId,
required Set<int> walletIds,
}) async {
final current = await readClientWalletAccess(connection, clientId: clientId);
final toGrant = walletIds.difference(current);
final toRevoke = current.difference(walletIds);
if (toGrant.isNotEmpty) {
await connection.tell(
UserAgentRequest(
sdkClient: ua_sdk.Request(
grantWalletAccess: ua_sdk.GrantWalletAccess(
accesses: [
for (final walletId in toGrant)
ua_sdk.WalletAccess(sdkClientId: clientId, walletId: walletId),
],
),
),
),
);
}
if (toRevoke.isNotEmpty) {
await connection.tell(
UserAgentRequest(
sdkClient: ua_sdk.Request(
revokeWalletAccess: ua_sdk.RevokeWalletAccess(
accesses: [for (final walletId in toRevoke) walletId],
),
),
),
);
}
}

View File

@@ -1,62 +1,62 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:json_annotation/json_annotation.dart';
part 'server_info_storage.g.dart';
@JsonSerializable()
class StoredServerInfo {
const StoredServerInfo({
required this.address,
required this.port,
required this.caCertFingerprint,
});
final String address;
final int port;
final String caCertFingerprint;
factory StoredServerInfo.fromJson(Map<String, dynamic> json) =>
_$StoredServerInfoFromJson(json);
Map<String, dynamic> toJson() => _$StoredServerInfoToJson(this);
}
abstract class ServerInfoStorage {
Future<StoredServerInfo?> load();
Future<void> save(StoredServerInfo serverInfo);
Future<void> clear();
}
class SecureServerInfoStorage implements ServerInfoStorage {
static const _storageKey = 'server_info';
const SecureServerInfoStorage();
static const _storage = FlutterSecureStorage();
@override
Future<StoredServerInfo?> load() async {
return null;
final rawValue = await _storage.read(key: _storageKey);
if (rawValue == null) {
return null;
}
final decoded = jsonDecode(rawValue) as Map<String, dynamic>;
return StoredServerInfo.fromJson(decoded);
}
@override
Future<void> save(StoredServerInfo serverInfo) {
return _storage.write(
key: _storageKey,
value: jsonEncode(serverInfo.toJson()),
);
}
@override
Future<void> clear() {
return _storage.delete(key: _storageKey);
}
}
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:json_annotation/json_annotation.dart';
part 'server_info_storage.g.dart';
@JsonSerializable()
class StoredServerInfo {
const StoredServerInfo({
required this.address,
required this.port,
required this.caCertFingerprint,
});
final String address;
final int port;
final String caCertFingerprint;
factory StoredServerInfo.fromJson(Map<String, dynamic> json) =>
_$StoredServerInfoFromJson(json);
Map<String, dynamic> toJson() => _$StoredServerInfoToJson(this);
}
abstract class ServerInfoStorage {
Future<StoredServerInfo?> load();
Future<void> save(StoredServerInfo serverInfo);
Future<void> clear();
}
class SecureServerInfoStorage implements ServerInfoStorage {
static const _storageKey = 'server_info';
const SecureServerInfoStorage();
static const _storage = FlutterSecureStorage();
@override
Future<StoredServerInfo?> load() async {
return null;
final rawValue = await _storage.read(key: _storageKey);
if (rawValue == null) {
return null;
}
final decoded = jsonDecode(rawValue) as Map<String, dynamic>;
return StoredServerInfo.fromJson(decoded);
}
@override
Future<void> save(StoredServerInfo serverInfo) {
return _storage.write(
key: _storageKey,
value: jsonEncode(serverInfo.toJson()),
);
}
@override
Future<void> clear() {
return _storage.delete(key: _storageKey);
}
}

View File

@@ -1,21 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_info_storage.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
StoredServerInfo _$StoredServerInfoFromJson(Map<String, dynamic> json) =>
StoredServerInfo(
address: json['address'] as String,
port: (json['port'] as num).toInt(),
caCertFingerprint: json['caCertFingerprint'] as String,
);
Map<String, dynamic> _$StoredServerInfoToJson(StoredServerInfo instance) =>
<String, dynamic>{
'address': instance.address,
'port': instance.port,
'caCertFingerprint': instance.caCertFingerprint,
};
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_info_storage.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
StoredServerInfo _$StoredServerInfoFromJson(Map<String, dynamic> json) =>
StoredServerInfo(
address: json['address'] as String,
port: (json['port'] as num).toInt(),
caCertFingerprint: json['caCertFingerprint'] as String,
);
Map<String, dynamic> _$StoredServerInfoToJson(StoredServerInfo instance) =>
<String, dynamic>{
'address': instance.address,
'port': instance.port,
'caCertFingerprint': instance.caCertFingerprint,
};

View File

@@ -1,160 +1,160 @@
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/user_agent/vault/bootstrap.pb.dart'
as ua_bootstrap;
import 'package:arbiter/proto/user_agent/vault/unseal.pb.dart' as ua_unseal;
import 'package:arbiter/proto/user_agent/vault/vault.pb.dart' as ua_vault;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:cryptography/cryptography.dart';
const _vaultKeyAssociatedData = 'arbiter.vault.password';
Future<ua_bootstrap.BootstrapResult> bootstrapVault(
Connection connection,
String password,
) async {
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
final response = await connection.ask(
UserAgentRequest(
vault: ua_vault.Request(
bootstrap: ua_bootstrap.Request(
encryptedKey: ua_bootstrap.BootstrapEncryptedKey(
nonce: encryptedKey.nonce,
ciphertext: encryptedKey.ciphertext,
associatedData: encryptedKey.associatedData,
),
),
),
),
);
if (!response.hasVault()) {
throw Exception('Expected vault response, got ${response.whichPayload()}');
}
final vaultResponse = response.vault;
if (!vaultResponse.hasBootstrap()) {
throw Exception(
'Expected bootstrap result, got ${vaultResponse.whichPayload()}',
);
}
final bootstrapResponse = vaultResponse.bootstrap;
if (!bootstrapResponse.hasResult()) {
throw Exception('Expected bootstrap result payload.');
}
return bootstrapResponse.result;
}
Future<ua_unseal.UnsealResult> unsealVault(
Connection connection,
String password,
) async {
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
final response = await connection.ask(
UserAgentRequest(
vault: ua_vault.Request(
unseal: ua_unseal.Request(
encryptedKey: ua_unseal.UnsealEncryptedKey(
nonce: encryptedKey.nonce,
ciphertext: encryptedKey.ciphertext,
associatedData: encryptedKey.associatedData,
),
),
),
),
);
if (!response.hasVault()) {
throw Exception('Expected vault response, got ${response.whichPayload()}');
}
final vaultResponse = response.vault;
if (!vaultResponse.hasUnseal()) {
throw Exception(
'Expected unseal result, got ${vaultResponse.whichPayload()}',
);
}
final unsealResponse = vaultResponse.unseal;
if (!unsealResponse.hasResult()) {
throw Exception(
'Expected unseal result payload, got ${unsealResponse.whichPayload()}',
);
}
return unsealResponse.result;
}
Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
Connection connection,
String password,
) async {
final keyExchange = X25519();
final cipher = Xchacha20.poly1305Aead();
final clientKeyPair = await keyExchange.newKeyPair();
final clientPublicKey = await clientKeyPair.extractPublicKey();
final handshakeResponse = await connection.ask(
UserAgentRequest(
vault: ua_vault.Request(
unseal: ua_unseal.Request(
start: ua_unseal.UnsealStart(clientPubkey: clientPublicKey.bytes),
),
),
),
);
if (!handshakeResponse.hasVault()) {
throw Exception(
'Expected vault response, got ${handshakeResponse.whichPayload()}',
);
}
final vaultResponse = handshakeResponse.vault;
if (!vaultResponse.hasUnseal()) {
throw Exception(
'Expected unseal handshake response, got ${vaultResponse.whichPayload()}',
);
}
final unsealResponse = vaultResponse.unseal;
if (!unsealResponse.hasStart()) {
throw Exception(
'Expected unseal handshake payload, got ${unsealResponse.whichPayload()}',
);
}
final serverPublicKey = SimplePublicKey(
unsealResponse.start.serverPubkey,
type: KeyPairType.x25519,
);
final sharedSecret = await keyExchange.sharedSecretKey(
keyPair: clientKeyPair,
remotePublicKey: serverPublicKey,
);
final secretBox = await cipher.encrypt(
password.codeUnits,
secretKey: sharedSecret,
nonce: cipher.newNonce(),
aad: _vaultKeyAssociatedData.codeUnits,
);
return _EncryptedVaultKey(
nonce: secretBox.nonce,
ciphertext: [...secretBox.cipherText, ...secretBox.mac.bytes],
associatedData: _vaultKeyAssociatedData.codeUnits,
);
}
class _EncryptedVaultKey {
const _EncryptedVaultKey({
required this.nonce,
required this.ciphertext,
required this.associatedData,
});
final List<int> nonce;
final List<int> ciphertext;
final List<int> associatedData;
}
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/proto/user_agent/vault/bootstrap.pb.dart'
as ua_bootstrap;
import 'package:arbiter/proto/user_agent/vault/unseal.pb.dart' as ua_unseal;
import 'package:arbiter/proto/user_agent/vault/vault.pb.dart' as ua_vault;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:cryptography/cryptography.dart';
const _vaultKeyAssociatedData = 'arbiter.vault.password';
Future<ua_bootstrap.BootstrapResult> bootstrapVault(
Connection connection,
String password,
) async {
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
final response = await connection.ask(
UserAgentRequest(
vault: ua_vault.Request(
bootstrap: ua_bootstrap.Request(
encryptedKey: ua_bootstrap.BootstrapEncryptedKey(
nonce: encryptedKey.nonce,
ciphertext: encryptedKey.ciphertext,
associatedData: encryptedKey.associatedData,
),
),
),
),
);
if (!response.hasVault()) {
throw Exception('Expected vault response, got ${response.whichPayload()}');
}
final vaultResponse = response.vault;
if (!vaultResponse.hasBootstrap()) {
throw Exception(
'Expected bootstrap result, got ${vaultResponse.whichPayload()}',
);
}
final bootstrapResponse = vaultResponse.bootstrap;
if (!bootstrapResponse.hasResult()) {
throw Exception('Expected bootstrap result payload.');
}
return bootstrapResponse.result;
}
Future<ua_unseal.UnsealResult> unsealVault(
Connection connection,
String password,
) async {
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
final response = await connection.ask(
UserAgentRequest(
vault: ua_vault.Request(
unseal: ua_unseal.Request(
encryptedKey: ua_unseal.UnsealEncryptedKey(
nonce: encryptedKey.nonce,
ciphertext: encryptedKey.ciphertext,
associatedData: encryptedKey.associatedData,
),
),
),
),
);
if (!response.hasVault()) {
throw Exception('Expected vault response, got ${response.whichPayload()}');
}
final vaultResponse = response.vault;
if (!vaultResponse.hasUnseal()) {
throw Exception(
'Expected unseal result, got ${vaultResponse.whichPayload()}',
);
}
final unsealResponse = vaultResponse.unseal;
if (!unsealResponse.hasResult()) {
throw Exception(
'Expected unseal result payload, got ${unsealResponse.whichPayload()}',
);
}
return unsealResponse.result;
}
Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
Connection connection,
String password,
) async {
final keyExchange = X25519();
final cipher = Xchacha20.poly1305Aead();
final clientKeyPair = await keyExchange.newKeyPair();
final clientPublicKey = await clientKeyPair.extractPublicKey();
final handshakeResponse = await connection.ask(
UserAgentRequest(
vault: ua_vault.Request(
unseal: ua_unseal.Request(
start: ua_unseal.UnsealStart(clientPubkey: clientPublicKey.bytes),
),
),
),
);
if (!handshakeResponse.hasVault()) {
throw Exception(
'Expected vault response, got ${handshakeResponse.whichPayload()}',
);
}
final vaultResponse = handshakeResponse.vault;
if (!vaultResponse.hasUnseal()) {
throw Exception(
'Expected unseal handshake response, got ${vaultResponse.whichPayload()}',
);
}
final unsealResponse = vaultResponse.unseal;
if (!unsealResponse.hasStart()) {
throw Exception(
'Expected unseal handshake payload, got ${unsealResponse.whichPayload()}',
);
}
final serverPublicKey = SimplePublicKey(
unsealResponse.start.serverPubkey,
type: KeyPairType.x25519,
);
final sharedSecret = await keyExchange.sharedSecretKey(
keyPair: clientKeyPair,
remotePublicKey: serverPublicKey,
);
final secretBox = await cipher.encrypt(
password.codeUnits,
secretKey: sharedSecret,
nonce: cipher.newNonce(),
aad: _vaultKeyAssociatedData.codeUnits,
);
return _EncryptedVaultKey(
nonce: secretBox.nonce,
ciphertext: [...secretBox.cipherText, ...secretBox.mac.bytes],
associatedData: _vaultKeyAssociatedData.codeUnits,
);
}
class _EncryptedVaultKey {
const _EncryptedVaultKey({
required this.nonce,
required this.ciphertext,
required this.associatedData,
});
final List<int> nonce;
final List<int> ciphertext;
final List<int> associatedData;
}