From 3706a36cf9bb04267f0ec3f1931fa92e29d79d92 Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 6 Nov 2025 20:50:22 +0100 Subject: [PATCH] implementing pow #253 --- lib/app.dart | 16 +++ .../api/websocket/client_to_server.pb.dart | 110 ++++++++++++++---- .../websocket/client_to_server.pbjson.dart | 49 ++++---- .../protobuf/api/websocket/error.pbenum.dart | 4 + .../protobuf/api/websocket/error.pbjson.dart | 5 +- .../api/websocket/server_to_client.pb.dart | 84 ++++++++++++- .../websocket/server_to_client.pbjson.dart | 54 +++++---- lib/src/services/api.service.dart | 22 +++- lib/src/utils/pow.dart | 3 +- lib/src/views/onboarding/register.view.dart | 34 +++++- test/unit_test.dart | 3 +- 11 files changed, 310 insertions(+), 74 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index aa43d90..52371c3 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -7,6 +7,8 @@ import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/providers/settings.provider.dart'; import 'package:twonly/src/services/subscription.service.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/app_outdated.dart'; import 'package:twonly/src/views/home.view.dart'; @@ -154,6 +156,8 @@ class _AppMainWidgetState extends State { bool _showOnboarding = true; bool _isLoaded = false; + Future? _proofOfWork; + @override void initState() { initAsync(); @@ -169,6 +173,17 @@ class _AppMainWidgetState extends State { } } + if (!_isUserCreated && !_showDatabaseMigration) { + // This means the user is in the onboarding screen, so start with the Proof of Work. + + final proof = await apiService.getProofOfWork(); + if (proof != null) { + Log.info('Starting with proof of work calculation.'); + // Starting with the proof of work. + _proofOfWork = calculatePoW(proof.prefix, proof.difficulty.toInt()); + } + } + setState(() { _isLoaded = true; }); @@ -205,6 +220,7 @@ class _AppMainWidgetState extends State { } else { child = RegisterView( callbackOnSuccess: initAsync, + proofOfWork: _proofOfWork, ); } diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart index c986530..db2849b 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart @@ -196,6 +196,38 @@ class V0 extends $pb.GeneratedMessage { Response ensureResponse() => $_ensure(3); } +class Handshake_RequestPOW extends $pb.GeneratedMessage { + factory Handshake_RequestPOW() => create(); + Handshake_RequestPOW._() : super(); + factory Handshake_RequestPOW.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Handshake_RequestPOW.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Handshake.RequestPOW', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Handshake_RequestPOW clone() => Handshake_RequestPOW()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Handshake_RequestPOW copyWith(void Function(Handshake_RequestPOW) updates) => super.copyWith((message) => updates(message as Handshake_RequestPOW)) as Handshake_RequestPOW; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Handshake_RequestPOW create() => Handshake_RequestPOW._(); + Handshake_RequestPOW createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Handshake_RequestPOW getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Handshake_RequestPOW? _defaultInstance; +} + class Handshake_Register extends $pb.GeneratedMessage { factory Handshake_Register({ $core.String? username, @@ -207,6 +239,7 @@ class Handshake_Register extends $pb.GeneratedMessage { $fixnum.Int64? registrationId, $core.bool? isIos, $core.String? langCode, + $fixnum.Int64? proofOfWork, }) { final $result = create(); if (username != null) { @@ -236,6 +269,9 @@ class Handshake_Register extends $pb.GeneratedMessage { if (langCode != null) { $result.langCode = langCode; } + if (proofOfWork != null) { + $result.proofOfWork = proofOfWork; + } return $result; } Handshake_Register._() : super(); @@ -252,6 +288,7 @@ class Handshake_Register extends $pb.GeneratedMessage { ..aInt64(7, _omitFieldNames ? '' : 'registrationId') ..aOB(8, _omitFieldNames ? '' : 'isIos') ..aOS(9, _omitFieldNames ? '' : 'langCode') + ..aInt64(10, _omitFieldNames ? '' : 'proofOfWork') ..hasRequiredFields = false ; @@ -356,6 +393,15 @@ class Handshake_Register extends $pb.GeneratedMessage { $core.bool hasLangCode() => $_has(8); @$pb.TagNumber(9) void clearLangCode() => clearField(9); + + @$pb.TagNumber(10) + $fixnum.Int64 get proofOfWork => $_getI64(9); + @$pb.TagNumber(10) + set proofOfWork($fixnum.Int64 v) { $_setInt64(9, v); } + @$pb.TagNumber(10) + $core.bool hasProofOfWork() => $_has(9); + @$pb.TagNumber(10) + void clearProofOfWork() => clearField(10); } class Handshake_GetAuthChallenge extends $pb.GeneratedMessage { @@ -548,32 +594,37 @@ class Handshake_Authenticate extends $pb.GeneratedMessage { enum Handshake_Handshake { register, - getauthchallenge, - getauthtoken, + getAuthChallenge, + getAuthToken, authenticate, + requestPOW, notSet } class Handshake extends $pb.GeneratedMessage { factory Handshake({ Handshake_Register? register, - Handshake_GetAuthChallenge? getauthchallenge, - Handshake_GetAuthToken? getauthtoken, + Handshake_GetAuthChallenge? getAuthChallenge, + Handshake_GetAuthToken? getAuthToken, Handshake_Authenticate? authenticate, + Handshake_RequestPOW? requestPOW, }) { final $result = create(); if (register != null) { $result.register = register; } - if (getauthchallenge != null) { - $result.getauthchallenge = getauthchallenge; + if (getAuthChallenge != null) { + $result.getAuthChallenge = getAuthChallenge; } - if (getauthtoken != null) { - $result.getauthtoken = getauthtoken; + if (getAuthToken != null) { + $result.getAuthToken = getAuthToken; } if (authenticate != null) { $result.authenticate = authenticate; } + if (requestPOW != null) { + $result.requestPOW = requestPOW; + } return $result; } Handshake._() : super(); @@ -582,17 +633,19 @@ class Handshake extends $pb.GeneratedMessage { static const $core.Map<$core.int, Handshake_Handshake> _Handshake_HandshakeByTag = { 1 : Handshake_Handshake.register, - 2 : Handshake_Handshake.getauthchallenge, - 3 : Handshake_Handshake.getauthtoken, + 2 : Handshake_Handshake.getAuthChallenge, + 3 : Handshake_Handshake.getAuthToken, 4 : Handshake_Handshake.authenticate, + 5 : Handshake_Handshake.requestPOW, 0 : Handshake_Handshake.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Handshake', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) - ..oo(0, [1, 2, 3, 4]) + ..oo(0, [1, 2, 3, 4, 5]) ..aOM(1, _omitFieldNames ? '' : 'register', subBuilder: Handshake_Register.create) - ..aOM(2, _omitFieldNames ? '' : 'getauthchallenge', subBuilder: Handshake_GetAuthChallenge.create) - ..aOM(3, _omitFieldNames ? '' : 'getauthtoken', subBuilder: Handshake_GetAuthToken.create) + ..aOM(2, _omitFieldNames ? '' : 'getAuthChallenge', protoName: 'getAuthChallenge', subBuilder: Handshake_GetAuthChallenge.create) + ..aOM(3, _omitFieldNames ? '' : 'getAuthToken', protoName: 'getAuthToken', subBuilder: Handshake_GetAuthToken.create) ..aOM(4, _omitFieldNames ? '' : 'authenticate', subBuilder: Handshake_Authenticate.create) + ..aOM(5, _omitFieldNames ? '' : 'requestPOW', protoName: 'requestPOW', subBuilder: Handshake_RequestPOW.create) ..hasRequiredFields = false ; @@ -632,26 +685,26 @@ class Handshake extends $pb.GeneratedMessage { Handshake_Register ensureRegister() => $_ensure(0); @$pb.TagNumber(2) - Handshake_GetAuthChallenge get getauthchallenge => $_getN(1); + Handshake_GetAuthChallenge get getAuthChallenge => $_getN(1); @$pb.TagNumber(2) - set getauthchallenge(Handshake_GetAuthChallenge v) { setField(2, v); } + set getAuthChallenge(Handshake_GetAuthChallenge v) { setField(2, v); } @$pb.TagNumber(2) - $core.bool hasGetauthchallenge() => $_has(1); + $core.bool hasGetAuthChallenge() => $_has(1); @$pb.TagNumber(2) - void clearGetauthchallenge() => clearField(2); + void clearGetAuthChallenge() => clearField(2); @$pb.TagNumber(2) - Handshake_GetAuthChallenge ensureGetauthchallenge() => $_ensure(1); + Handshake_GetAuthChallenge ensureGetAuthChallenge() => $_ensure(1); @$pb.TagNumber(3) - Handshake_GetAuthToken get getauthtoken => $_getN(2); + Handshake_GetAuthToken get getAuthToken => $_getN(2); @$pb.TagNumber(3) - set getauthtoken(Handshake_GetAuthToken v) { setField(3, v); } + set getAuthToken(Handshake_GetAuthToken v) { setField(3, v); } @$pb.TagNumber(3) - $core.bool hasGetauthtoken() => $_has(2); + $core.bool hasGetAuthToken() => $_has(2); @$pb.TagNumber(3) - void clearGetauthtoken() => clearField(3); + void clearGetAuthToken() => clearField(3); @$pb.TagNumber(3) - Handshake_GetAuthToken ensureGetauthtoken() => $_ensure(2); + Handshake_GetAuthToken ensureGetAuthToken() => $_ensure(2); @$pb.TagNumber(4) Handshake_Authenticate get authenticate => $_getN(3); @@ -663,6 +716,17 @@ class Handshake extends $pb.GeneratedMessage { void clearAuthenticate() => clearField(4); @$pb.TagNumber(4) Handshake_Authenticate ensureAuthenticate() => $_ensure(3); + + @$pb.TagNumber(5) + Handshake_RequestPOW get requestPOW => $_getN(4); + @$pb.TagNumber(5) + set requestPOW(Handshake_RequestPOW v) { setField(5, v); } + @$pb.TagNumber(5) + $core.bool hasRequestPOW() => $_has(4); + @$pb.TagNumber(5) + void clearRequestPOW() => clearField(5); + @$pb.TagNumber(5) + Handshake_RequestPOW ensureRequestPOW() => $_ensure(4); } class ApplicationData_TextMessage extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart index c95798c..b6cfd08 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart @@ -56,16 +56,22 @@ const Handshake$json = { '1': 'Handshake', '2': [ {'1': 'register', '3': 1, '4': 1, '5': 11, '6': '.client_to_server.Handshake.Register', '9': 0, '10': 'register'}, - {'1': 'getauthchallenge', '3': 2, '4': 1, '5': 11, '6': '.client_to_server.Handshake.GetAuthChallenge', '9': 0, '10': 'getauthchallenge'}, - {'1': 'getauthtoken', '3': 3, '4': 1, '5': 11, '6': '.client_to_server.Handshake.GetAuthToken', '9': 0, '10': 'getauthtoken'}, + {'1': 'getAuthChallenge', '3': 2, '4': 1, '5': 11, '6': '.client_to_server.Handshake.GetAuthChallenge', '9': 0, '10': 'getAuthChallenge'}, + {'1': 'getAuthToken', '3': 3, '4': 1, '5': 11, '6': '.client_to_server.Handshake.GetAuthToken', '9': 0, '10': 'getAuthToken'}, {'1': 'authenticate', '3': 4, '4': 1, '5': 11, '6': '.client_to_server.Handshake.Authenticate', '9': 0, '10': 'authenticate'}, + {'1': 'requestPOW', '3': 5, '4': 1, '5': 11, '6': '.client_to_server.Handshake.RequestPOW', '9': 0, '10': 'requestPOW'}, ], - '3': [Handshake_Register$json, Handshake_GetAuthChallenge$json, Handshake_GetAuthToken$json, Handshake_Authenticate$json], + '3': [Handshake_RequestPOW$json, Handshake_Register$json, Handshake_GetAuthChallenge$json, Handshake_GetAuthToken$json, Handshake_Authenticate$json], '8': [ {'1': 'Handshake'}, ], }; +@$core.Deprecated('Use handshakeDescriptor instead') +const Handshake_RequestPOW$json = { + '1': 'RequestPOW', +}; + @$core.Deprecated('Use handshakeDescriptor instead') const Handshake_Register$json = { '1': 'Register', @@ -79,6 +85,7 @@ const Handshake_Register$json = { {'1': 'registration_id', '3': 7, '4': 1, '5': 3, '10': 'registrationId'}, {'1': 'is_ios', '3': 8, '4': 1, '5': 8, '10': 'isIos'}, {'1': 'lang_code', '3': 9, '4': 1, '5': 9, '10': 'langCode'}, + {'1': 'proof_of_work', '3': 10, '4': 1, '5': 3, '10': 'proofOfWork'}, ], '8': [ {'1': '_invite_code'}, @@ -117,23 +124,25 @@ const Handshake_Authenticate$json = { /// Descriptor for `Handshake`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode( 'CglIYW5kc2hha2USQgoIcmVnaXN0ZXIYASABKAsyJC5jbGllbnRfdG9fc2VydmVyLkhhbmRzaG' - 'FrZS5SZWdpc3RlckgAUghyZWdpc3RlchJaChBnZXRhdXRoY2hhbGxlbmdlGAIgASgLMiwuY2xp' - 'ZW50X3RvX3NlcnZlci5IYW5kc2hha2UuR2V0QXV0aENoYWxsZW5nZUgAUhBnZXRhdXRoY2hhbG' - 'xlbmdlEk4KDGdldGF1dGh0b2tlbhgDIAEoCzIoLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtl' - 'LkdldEF1dGhUb2tlbkgAUgxnZXRhdXRodG9rZW4STgoMYXV0aGVudGljYXRlGAQgASgLMiguY2' - 'xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlSABSDGF1dGhlbnRpY2F0ZRrw' - 'AgoIUmVnaXN0ZXISGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lEiQKC2ludml0ZV9jb2RlGA' - 'IgASgJSABSCmludml0ZUNvZGWIAQESLgoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDFIRcHVi' - 'bGljSWRlbnRpdHlLZXkSIwoNc2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EjYKF3' - 'NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAUgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUSKAoQ' - 'c2lnbmVkX3ByZWtleV9pZBgGIAEoA1IOc2lnbmVkUHJla2V5SWQSJwoPcmVnaXN0cmF0aW9uX2' - 'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZBIVCgZpc19pb3MYCCABKAhSBWlzSW9zEhsKCWxhbmdf' - 'Y29kZRgJIAEoCVIIbGFuZ0NvZGVCDgoMX2ludml0ZV9jb2RlGhIKEEdldEF1dGhDaGFsbGVuZ2' - 'UaQwoMR2V0QXV0aFRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIaCghyZXNwb25zZRgC' - 'IAEoDFIIcmVzcG9uc2UarAEKDEF1dGhlbnRpY2F0ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySW' - 'QSHQoKYXV0aF90b2tlbhgCIAEoDFIJYXV0aFRva2VuEiQKC2FwcF92ZXJzaW9uGAMgASgJSABS' - 'CmFwcFZlcnNpb26IAQESIAoJZGV2aWNlX2lkGAQgASgDSAFSCGRldmljZUlkiAEBQg4KDF9hcH' - 'BfdmVyc2lvbkIMCgpfZGV2aWNlX2lkQgsKCUhhbmRzaGFrZQ=='); + 'FrZS5SZWdpc3RlckgAUghyZWdpc3RlchJaChBnZXRBdXRoQ2hhbGxlbmdlGAIgASgLMiwuY2xp' + 'ZW50X3RvX3NlcnZlci5IYW5kc2hha2UuR2V0QXV0aENoYWxsZW5nZUgAUhBnZXRBdXRoQ2hhbG' + 'xlbmdlEk4KDGdldEF1dGhUb2tlbhgDIAEoCzIoLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtl' + 'LkdldEF1dGhUb2tlbkgAUgxnZXRBdXRoVG9rZW4STgoMYXV0aGVudGljYXRlGAQgASgLMiguY2' + 'xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlSABSDGF1dGhlbnRpY2F0ZRJI' + 'CgpyZXF1ZXN0UE9XGAUgASgLMiYuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuUmVxdWVzdF' + 'BPV0gAUgpyZXF1ZXN0UE9XGgwKClJlcXVlc3RQT1calAMKCFJlZ2lzdGVyEhoKCHVzZXJuYW1l' + 'GAEgASgJUgh1c2VybmFtZRIkCgtpbnZpdGVfY29kZRgCIAEoCUgAUgppbnZpdGVDb2RliAEBEi' + '4KE3B1YmxpY19pZGVudGl0eV9rZXkYAyABKAxSEXB1YmxpY0lkZW50aXR5S2V5EiMKDXNpZ25l' + 'ZF9wcmVrZXkYBCABKAxSDHNpZ25lZFByZWtleRI2ChdzaWduZWRfcHJla2V5X3NpZ25hdHVyZR' + 'gFIAEoDFIVc2lnbmVkUHJla2V5U2lnbmF0dXJlEigKEHNpZ25lZF9wcmVrZXlfaWQYBiABKANS' + 'DnNpZ25lZFByZWtleUlkEicKD3JlZ2lzdHJhdGlvbl9pZBgHIAEoA1IOcmVnaXN0cmF0aW9uSW' + 'QSFQoGaXNfaW9zGAggASgIUgVpc0lvcxIbCglsYW5nX2NvZGUYCSABKAlSCGxhbmdDb2RlEiIK' + 'DXByb29mX29mX3dvcmsYCiABKANSC3Byb29mT2ZXb3JrQg4KDF9pbnZpdGVfY29kZRoSChBHZX' + 'RBdXRoQ2hhbGxlbmdlGkMKDEdldEF1dGhUb2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQS' + 'GgoIcmVzcG9uc2UYAiABKAxSCHJlc3BvbnNlGqwBCgxBdXRoZW50aWNhdGUSFwoHdXNlcl9pZB' + 'gBIAEoA1IGdXNlcklkEh0KCmF1dGhfdG9rZW4YAiABKAxSCWF1dGhUb2tlbhIkCgthcHBfdmVy' + 'c2lvbhgDIAEoCUgAUgphcHBWZXJzaW9uiAEBEiAKCWRldmljZV9pZBgEIAEoA0gBUghkZXZpY2' + 'VJZIgBAUIOCgxfYXBwX3ZlcnNpb25CDAoKX2RldmljZV9pZEILCglIYW5kc2hha2U='); @$core.Deprecated('Use applicationDataDescriptor instead') const ApplicationData$json = { diff --git a/lib/src/model/protobuf/api/websocket/error.pbenum.dart b/lib/src/model/protobuf/api/websocket/error.pbenum.dart index 3e9c5e0..1c5331e 100644 --- a/lib/src/model/protobuf/api/websocket/error.pbenum.dart +++ b/lib/src/model/protobuf/api/websocket/error.pbenum.dart @@ -48,6 +48,8 @@ class ErrorCode extends $pb.ProtobufEnum { static const ErrorCode UserIdAlreadyTaken = ErrorCode._(1029, _omitEnumNames ? '' : 'UserIdAlreadyTaken'); static const ErrorCode AppVersionOutdated = ErrorCode._(1030, _omitEnumNames ? '' : 'AppVersionOutdated'); static const ErrorCode NewDeviceRegistered = ErrorCode._(1031, _omitEnumNames ? '' : 'NewDeviceRegistered'); + static const ErrorCode InvalidProofOfWork = ErrorCode._(1032, _omitEnumNames ? '' : 'InvalidProofOfWork'); + static const ErrorCode RegistrationDisabled = ErrorCode._(1033, _omitEnumNames ? '' : 'RegistrationDisabled'); static const $core.List values = [ Unknown, @@ -84,6 +86,8 @@ class ErrorCode extends $pb.ProtobufEnum { UserIdAlreadyTaken, AppVersionOutdated, NewDeviceRegistered, + InvalidProofOfWork, + RegistrationDisabled, ]; static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/lib/src/model/protobuf/api/websocket/error.pbjson.dart b/lib/src/model/protobuf/api/websocket/error.pbjson.dart index fad129d..2e937dd 100644 --- a/lib/src/model/protobuf/api/websocket/error.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/error.pbjson.dart @@ -51,6 +51,8 @@ const ErrorCode$json = { {'1': 'UserIdAlreadyTaken', '2': 1029}, {'1': 'AppVersionOutdated', '2': 1030}, {'1': 'NewDeviceRegistered', '2': 1031}, + {'1': 'InvalidProofOfWork', '2': 1032}, + {'1': 'RegistrationDisabled', '2': 1033}, ], }; @@ -70,5 +72,6 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode( 'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q' 'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW' 'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu' - 'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcI'); + 'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcIEh' + 'cKEkludmFsaWRQcm9vZk9mV29yaxCICBIZChRSZWdpc3RyYXRpb25EaXNhYmxlZBCJCA=='); diff --git a/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart b/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart index 8d67382..381c100 100644 --- a/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart +++ b/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart @@ -1536,6 +1536,70 @@ class Response_DownloadTokens extends $pb.GeneratedMessage { $core.List<$core.List<$core.int>> get downloadTokens => $_getList(0); } +class Response_ProofOfWork extends $pb.GeneratedMessage { + factory Response_ProofOfWork({ + $core.String? prefix, + $fixnum.Int64? difficulty, + }) { + final $result = create(); + if (prefix != null) { + $result.prefix = prefix; + } + if (difficulty != null) { + $result.difficulty = difficulty; + } + return $result; + } + Response_ProofOfWork._() : super(); + factory Response_ProofOfWork.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Response_ProofOfWork.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Response.ProofOfWork', package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'prefix') + ..aInt64(2, _omitFieldNames ? '' : 'difficulty') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Response_ProofOfWork clone() => Response_ProofOfWork()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Response_ProofOfWork copyWith(void Function(Response_ProofOfWork) updates) => super.copyWith((message) => updates(message as Response_ProofOfWork)) as Response_ProofOfWork; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Response_ProofOfWork create() => Response_ProofOfWork._(); + Response_ProofOfWork createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Response_ProofOfWork getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Response_ProofOfWork? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get prefix => $_getSZ(0); + @$pb.TagNumber(1) + set prefix($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasPrefix() => $_has(0); + @$pb.TagNumber(1) + void clearPrefix() => clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get difficulty => $_getI64(1); + @$pb.TagNumber(2) + set difficulty($fixnum.Int64 v) { $_setInt64(1, v); } + @$pb.TagNumber(2) + $core.bool hasDifficulty() => $_has(1); + @$pb.TagNumber(2) + void clearDifficulty() => clearField(2); +} + enum Response_Ok_Ok { none, userid, @@ -1551,6 +1615,7 @@ enum Response_Ok_Ok { addaccountsinvites, downloadtokens, signedprekey, + proofOfWork, notSet } @@ -1570,6 +1635,7 @@ class Response_Ok extends $pb.GeneratedMessage { Response_AddAccountsInvites? addaccountsinvites, Response_DownloadTokens? downloadtokens, Response_SignedPreKey? signedprekey, + Response_ProofOfWork? proofOfWork, }) { final $result = create(); if (none != null) { @@ -1614,6 +1680,9 @@ class Response_Ok extends $pb.GeneratedMessage { if (signedprekey != null) { $result.signedprekey = signedprekey; } + if (proofOfWork != null) { + $result.proofOfWork = proofOfWork; + } return $result; } Response_Ok._() : super(); @@ -1635,10 +1704,11 @@ class Response_Ok extends $pb.GeneratedMessage { 12 : Response_Ok_Ok.addaccountsinvites, 13 : Response_Ok_Ok.downloadtokens, 14 : Response_Ok_Ok.signedprekey, + 15 : Response_Ok_Ok.proofOfWork, 0 : Response_Ok_Ok.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Response.Ok', package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create) - ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) + ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) ..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None') ..aInt64(2, _omitFieldNames ? '' : 'userid') ..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'authchallenge', $pb.PbFieldType.OY) @@ -1653,6 +1723,7 @@ class Response_Ok extends $pb.GeneratedMessage { ..aOM(12, _omitFieldNames ? '' : 'addaccountsinvites', subBuilder: Response_AddAccountsInvites.create) ..aOM(13, _omitFieldNames ? '' : 'downloadtokens', subBuilder: Response_DownloadTokens.create) ..aOM(14, _omitFieldNames ? '' : 'signedprekey', subBuilder: Response_SignedPreKey.create) + ..aOM(15, _omitFieldNames ? '' : 'proofOfWork', protoName: 'proofOfWork', subBuilder: Response_ProofOfWork.create) ..hasRequiredFields = false ; @@ -1825,6 +1896,17 @@ class Response_Ok extends $pb.GeneratedMessage { void clearSignedprekey() => clearField(14); @$pb.TagNumber(14) Response_SignedPreKey ensureSignedprekey() => $_ensure(13); + + @$pb.TagNumber(15) + Response_ProofOfWork get proofOfWork => $_getN(14); + @$pb.TagNumber(15) + set proofOfWork(Response_ProofOfWork v) { setField(15, v); } + @$pb.TagNumber(15) + $core.bool hasProofOfWork() => $_has(14); + @$pb.TagNumber(15) + void clearProofOfWork() => clearField(15); + @$pb.TagNumber(15) + Response_ProofOfWork ensureProofOfWork() => $_ensure(14); } enum Response_Response { diff --git a/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart b/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart index eabade8..441503e 100644 --- a/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart @@ -73,7 +73,7 @@ const Response$json = { {'1': 'ok', '3': 1, '4': 1, '5': 11, '6': '.server_to_client.Response.Ok', '9': 0, '10': 'ok'}, {'1': 'error', '3': 2, '4': 1, '5': 14, '6': '.error.ErrorCode', '9': 0, '10': 'error'}, ], - '3': [Response_Authenticated$json, Response_Plan$json, Response_Plans$json, Response_AddAccountsInvite$json, Response_AddAccountsInvites$json, Response_Transaction$json, Response_AdditionalAccount$json, Response_Voucher$json, Response_Vouchers$json, Response_PlanBallance$json, Response_Location$json, Response_PreKey$json, Response_SignedPreKey$json, Response_UserData$json, Response_UploadToken$json, Response_DownloadTokens$json, Response_Ok$json], + '3': [Response_Authenticated$json, Response_Plan$json, Response_Plans$json, Response_AddAccountsInvite$json, Response_AddAccountsInvites$json, Response_Transaction$json, Response_AdditionalAccount$json, Response_Voucher$json, Response_Vouchers$json, Response_PlanBallance$json, Response_Location$json, Response_PreKey$json, Response_SignedPreKey$json, Response_UserData$json, Response_UploadToken$json, Response_DownloadTokens$json, Response_ProofOfWork$json, Response_Ok$json], '4': [Response_TransactionTypes$json], '8': [ {'1': 'Response'}, @@ -257,6 +257,15 @@ const Response_DownloadTokens$json = { ], }; +@$core.Deprecated('Use responseDescriptor instead') +const Response_ProofOfWork$json = { + '1': 'ProofOfWork', + '2': [ + {'1': 'prefix', '3': 1, '4': 1, '5': 9, '10': 'prefix'}, + {'1': 'difficulty', '3': 2, '4': 1, '5': 3, '10': 'difficulty'}, + ], +}; + @$core.Deprecated('Use responseDescriptor instead') const Response_Ok$json = { '1': 'Ok', @@ -275,6 +284,7 @@ const Response_Ok$json = { {'1': 'addaccountsinvites', '3': 12, '4': 1, '5': 11, '6': '.server_to_client.Response.AddAccountsInvites', '9': 0, '10': 'addaccountsinvites'}, {'1': 'downloadtokens', '3': 13, '4': 1, '5': 11, '6': '.server_to_client.Response.DownloadTokens', '9': 0, '10': 'downloadtokens'}, {'1': 'signedprekey', '3': 14, '4': 1, '5': 11, '6': '.server_to_client.Response.SignedPreKey', '9': 0, '10': 'signedprekey'}, + {'1': 'proofOfWork', '3': 15, '4': 1, '5': 11, '6': '.server_to_client.Response.ProofOfWork', '9': 0, '10': 'proofOfWork'}, ], '8': [ {'1': 'Ok'}, @@ -352,24 +362,26 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode( '9zaWduZWRfcHJla2V5X2lkGlkKC1VwbG9hZFRva2VuEiEKDHVwbG9hZF90b2tlbhgBIAEoDFIL' 'dXBsb2FkVG9rZW4SJwoPZG93bmxvYWRfdG9rZW5zGAIgAygMUg5kb3dubG9hZFRva2Vucxo5Cg' '5Eb3dubG9hZFRva2VucxInCg9kb3dubG9hZF90b2tlbnMYASADKAxSDmRvd25sb2FkVG9rZW5z' - 'GvcGCgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSGAoGdXNlcmlkGAIgASgDSABSBnVzZXJpZB' - 'ImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9rZW4Y' - 'BCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG9hZH' - 'Rva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Vc2Vy' - 'RGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW4YBiABKAxIAFIJYXV0aHRva2VuEkEKCGxvY2' - 'F0aW9uGAcgASgLMiMuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Mb2NhdGlvbkgAUghsb2Nh' - 'dGlvbhJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS' - '5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSABKAsyIC5zZXJ2ZXJf' - 'dG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYWxsYW5jZRgKIAEoCz' - 'InLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABSDHBsYW5iYWxsYW5j' - 'ZRJBCgh2b3VjaGVycxgLIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuVm91Y2hlcn' - 'NIAFIIdm91Y2hlcnMSXwoSYWRkYWNjb3VudHNpbnZpdGVzGAwgASgLMi0uc2VydmVyX3RvX2Ns' - 'aWVudC5SZXNwb25zZS5BZGRBY2NvdW50c0ludml0ZXNIAFISYWRkYWNjb3VudHNpbnZpdGVzEl' - 'MKDmRvd25sb2FkdG9rZW5zGA0gASgLMikuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Eb3du' - 'bG9hZFRva2Vuc0gAUg5kb3dubG9hZHRva2VucxJNCgxzaWduZWRwcmVrZXkYDiABKAsyJy5zZX' - 'J2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlNpZ25lZFByZUtleUgAUgxzaWduZWRwcmVrZXlCBAoC' - 'T2silgEKEFRyYW5zYWN0aW9uVHlwZXMSCgoGUmVmdW5kEAASEwoPVm91Y2hlclJlZGVlbWVkEA' - 'ESEgoOVm91Y2hlckNyZWF0ZWQQAhIICgRDYXNoEAMSDwoLUGxhblVwZ3JhZGUQBBILCgdVbmtu' - 'b3duEAUSFAoQVGhhbmtzRm9yVGVzdGluZxAGEg8KC0F1dG9SZW5ld2FsEAdCCgoIUmVzcG9uc2' - 'U='); + 'GkUKC1Byb29mT2ZXb3JrEhYKBnByZWZpeBgBIAEoCVIGcHJlZml4Eh4KCmRpZmZpY3VsdHkYAi' + 'ABKANSCmRpZmZpY3VsdHkawwcKAk9rEhQKBE5vbmUYASABKAhIAFIETm9uZRIYCgZ1c2VyaWQY' + 'AiABKANIAFIGdXNlcmlkEiYKDWF1dGhjaGFsbGVuZ2UYAyABKAxIAFINYXV0aGNoYWxsZW5nZR' + 'JKCgt1cGxvYWR0b2tlbhgEIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuVXBsb2Fk' + 'VG9rZW5IAFILdXBsb2FkdG9rZW4SQQoIdXNlcmRhdGEYBSABKAsyIy5zZXJ2ZXJfdG9fY2xpZW' + '50LlJlc3BvbnNlLlVzZXJEYXRhSABSCHVzZXJkYXRhEh4KCWF1dGh0b2tlbhgGIAEoDEgAUglh' + 'dXRodG9rZW4SQQoIbG9jYXRpb24YByABKAsyIy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk' + 'xvY2F0aW9uSABSCGxvY2F0aW9uElAKDWF1dGhlbnRpY2F0ZWQYCCABKAsyKC5zZXJ2ZXJfdG9f' + 'Y2xpZW50LlJlc3BvbnNlLkF1dGhlbnRpY2F0ZWRIAFINYXV0aGVudGljYXRlZBI4CgVwbGFucx' + 'gJIAEoCzIgLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbnNIAFIFcGxhbnMSTQoMcGxh' + 'bmJhbGxhbmNlGAogASgLMicuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuQmFsbGFuY2' + 'VIAFIMcGxhbmJhbGxhbmNlEkEKCHZvdWNoZXJzGAsgASgLMiMuc2VydmVyX3RvX2NsaWVudC5S' + 'ZXNwb25zZS5Wb3VjaGVyc0gAUgh2b3VjaGVycxJfChJhZGRhY2NvdW50c2ludml0ZXMYDCABKA' + 'syLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZEFjY291bnRzSW52aXRlc0gAUhJhZGRh' + 'Y2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0b2tlbnMYDSABKAsyKS5zZXJ2ZXJfdG9fY2xpZW' + '50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSABSDmRvd25sb2FkdG9rZW5zEk0KDHNpZ25lZHBy' + 'ZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuU2lnbmVkUHJlS2V5SABSDH' + 'NpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yaxgPIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVz' + 'cG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZPZldvcmtCBAoCT2silgEKEFRyYW5zYWN0aW9uVH' + 'lwZXMSCgoGUmVmdW5kEAASEwoPVm91Y2hlclJlZGVlbWVkEAESEgoOVm91Y2hlckNyZWF0ZWQQ' + 'AhIICgRDYXNoEAMSDwoLUGxhblVwZ3JhZGUQBBILCgdVbmtub3duEAUSFAoQVGhhbmtzRm9yVG' + 'VzdGluZxAGEg8KC0F1dG9SZW5ld2FsEAdCCgoIUmVzcG9uc2U='); diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 0d8a829..a918d29 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -415,7 +415,7 @@ class ApiService { } final handshake = Handshake() - ..getauthchallenge = Handshake_GetAuthChallenge(); + ..getAuthChallenge = Handshake_GetAuthChallenge(); final req = createClientToServerFromHandshake(handshake); final result = await sendRequestSync(req, authenticated: false); @@ -436,7 +436,7 @@ class ApiService { ..response = signature ..userId = Int64(userData.userId); - final getauthtoken = Handshake()..getauthtoken = getAuthToken; + final getauthtoken = Handshake()..getAuthToken = getAuthToken; final req2 = createClientToServerFromHandshake(getauthtoken); @@ -458,7 +458,11 @@ class ApiService { await tryAuthenticateWithToken(userData.userId); } - Future register(String username, String? inviteCode) async { + Future register( + String username, + String? inviteCode, + int proofOfWorkResult, + ) async { final signalIdentity = await getSignalIdentity(); if (signalIdentity == null) { return Result.error(ErrorCode.InternalError); @@ -477,6 +481,7 @@ class ApiService { ..signedPrekeySignature = signedPreKey.signature ..signedPrekeyId = Int64(signedPreKey.id) ..langCode = ui.PlatformDispatcher.instance.locale.languageCode + ..proofOfWork = Int64(proofOfWorkResult) ..isIos = Platform.isIOS; if (inviteCode != null && inviteCode != '') { @@ -503,6 +508,17 @@ class ApiService { return null; } + Future getProofOfWork() async { + final handshake = Handshake()..requestPOW = Handshake_RequestPOW(); + final req = createClientToServerFromHandshake(handshake); + final result = await sendRequestSync(req, authenticated: false); + if (result.isError) { + Log.error('could not request proof of work params', result); + return null; + } + return result.value.proofOfWork as Response_ProofOfWork; + } + Future downloadDone(List token) async { final get = ApplicationData_DownloadDone()..downloadToken = token; final appData = ApplicationData()..downloadDone = get; diff --git a/lib/src/utils/pow.dart b/lib/src/utils/pow.dart index 67d2ebe..b932a79 100644 --- a/lib/src/utils/pow.dart +++ b/lib/src/utils/pow.dart @@ -1,12 +1,11 @@ import 'package:cryptography_plus/cryptography_plus.dart'; -import 'package:drift/drift.dart'; bool isValid(int difficulty, List digest) { final bits = digest.map((i) => i.toRadixString(2).padLeft(8, '0')).join(); return bits.startsWith('0' * difficulty); } -Future calculatePoW(Uint8List prefix, int difficulty) async { +Future calculatePoW(String prefix, int difficulty) async { var i = 0; while (true) { i++; diff --git a/lib/src/views/onboarding/register.view.dart b/lib/src/views/onboarding/register.view.dart index e652f63..d06df15 100644 --- a/lib/src/views/onboarding/register.view.dart +++ b/lib/src/views/onboarding/register.view.dart @@ -13,14 +13,21 @@ import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; +import 'package:twonly/src/views/groups/group.view.dart'; import 'package:twonly/src/views/onboarding/recover.view.dart'; class RegisterView extends StatefulWidget { - const RegisterView({required this.callbackOnSuccess, super.key}); + const RegisterView({ + required this.callbackOnSuccess, + required this.proofOfWork, + super.key, + }); final Function callbackOnSuccess; + final Future? proofOfWork; @override State createState() => _RegisterViewState(); } @@ -48,11 +55,29 @@ class _RegisterViewState extends State { _showUserNameError = false; }); + late int proof; + + if (widget.proofOfWork != null) { + proof = await widget.proofOfWork!; + } else { + final pow = await apiService.getProofOfWork(); + if (pow == null) { + if (mounted) { + showNetworkIssue(context); + } + return; + // Starting with the proof of work. + } + proof = await calculatePoW(pow.prefix, pow.difficulty.toInt()); + } + + Log.info('The result of the POW is $proof'); + await createIfNotExistsSignalIdentity(); var userId = 0; - final res = await apiService.register(username, inviteCode); + final res = await apiService.register(username, inviteCode, proof); if (res.isSuccess) { Log.info('Got user_id ${res.value} from server'); userId = res.value.userid.toInt() as int; @@ -62,6 +87,11 @@ class _RegisterViewState extends State { await deleteLocalUserData(); return createNewUser(); } + if (res.error == ErrorCode.InvalidProofOfWork) { + Log.error('Proof of Work is invalid. Try again.'); + await deleteLocalUserData(); + return createNewUser(); + } if (mounted) { setState(() { _isTryingToRegister = false; diff --git a/test/unit_test.dart b/test/unit_test.dart index dafea36..cb4229d 100644 --- a/test/unit_test.dart +++ b/test/unit_test.dart @@ -15,7 +15,8 @@ void main() { }); test('test proof-of-work simple', () async { - expect(await calculatePoW(Uint8List.fromList([41, 41, 41, 41]), 6), 33); + // ignore: prefer_single_quotes + expect(await calculatePoW("testing", 10), 783); }); test('encode hex', () async {