Repository navigation
feat(reputation): add the reputation settings screen #677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
a55bd4b
feat(reputation): export and import reputation in the core
grunch 35c5de7
feat(reputation): import reputation from lnp2pBot
grunch 507c461
feat(reputation): add the reputation settings screen
grunch f172a3f
test: regenerate golden files
github-actions[bot] 145080b
fix(reputation): style the import screen as the design guide asks
grunch 88ee7ad
fix(reputation): style the reputation screen as the design guide asks
grunch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import 'dart:convert'; | ||
|
|
||
| /// lnp2pBot issues reputation attestations through Telegram | ||
| /// (MostroP2P/protocol reputation_attestation.md, "Telegram hand-off"). | ||
| const lnp2pbotUsername = 'lnp2pbot'; | ||
|
|
||
| final _hexKey = RegExp(r'^[0-9a-f]{64}$'); | ||
|
|
||
| /// The link that asks lnp2pBot to attest the user's reputation for | ||
| /// [identityHex], or null for a value that is not a hex key. The identity | ||
| /// travels as 43 characters of unpadded base64url so the start parameter | ||
| /// fits Telegram's 64-character cap. | ||
| Uri? lnp2pbotExportUri(String identityHex) { | ||
| final hex = identityHex.toLowerCase(); | ||
| if (!_hexKey.hasMatch(hex)) return null; | ||
| final bytes = [ | ||
| for (var i = 0; i < hex.length; i += 2) | ||
| int.parse(hex.substring(i, i + 2), radix: 16), | ||
| ]; | ||
| final encoded = base64Url.encode(bytes).replaceAll('=', ''); | ||
| return Uri.parse('https://t.me/$lnp2pbotUsername?start=rep_$encoded'); | ||
| } | ||
|
|
||
| /// The attestation event JSON inside pasted text: lnp2pBot sends it as a | ||
| /// message of its own, but a paste may carry surrounding text or whitespace. | ||
| /// Returns null when no JSON object is found. | ||
| String? extractAttestationJson(String text) { | ||
| final start = text.indexOf('{'); | ||
| final end = text.lastIndexOf('}'); | ||
| if (start < 0 || end <= start) return null; | ||
| final candidate = text.substring(start, end + 1); | ||
| try { | ||
| return jsonDecode(candidate) is Map<String, dynamic> ? candidate : null; | ||
| } on FormatException { | ||
| return null; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /// A 64-character hex public key as an `npub` (NIP-19 bech32), for showing | ||
| /// the user which identity they confirm. Returns null for anything else. | ||
| String? hexToNpub(String hex) { | ||
| if (!RegExp(r'^[0-9a-fA-F]{64}$').hasMatch(hex)) return null; | ||
| final bytes = [ | ||
| for (var i = 0; i < 64; i += 2) | ||
| int.parse(hex.substring(i, i + 2), radix: 16), | ||
| ]; | ||
| // Regroup 8-bit bytes into 5-bit words. | ||
| final words = <int>[]; | ||
| var acc = 0, bits = 0; | ||
| for (final b in bytes) { | ||
| acc = (acc << 8) | b; | ||
| bits += 8; | ||
| while (bits >= 5) { | ||
| bits -= 5; | ||
| words.add((acc >> bits) & 31); | ||
| } | ||
| } | ||
| if (bits > 0) words.add((acc << (5 - bits)) & 31); | ||
| const hrp = 'npub'; | ||
| const charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; | ||
| int polymod(List<int> values) { | ||
| const gen = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; | ||
| var chk = 1; | ||
| for (final v in values) { | ||
| final top = chk >> 25; | ||
| chk = ((chk & 0x1ffffff) << 5) ^ v; | ||
| for (var i = 0; i < 5; i++) { | ||
| if ((top >> i) & 1 == 1) chk ^= gen[i]; | ||
| } | ||
| } | ||
| return chk; | ||
| } | ||
|
|
||
| final expanded = [ | ||
| for (final c in hrp.codeUnits) c >> 5, | ||
| 0, | ||
| for (final c in hrp.codeUnits) c & 31, | ||
| ]; | ||
| final mod = polymod([...expanded, ...words, 0, 0, 0, 0, 0, 0]) ^ 1; | ||
| final checksum = [for (var i = 0; i < 6; i++) (mod >> (5 * (5 - i))) & 31]; | ||
| return '${hrp}1${[...words, ...checksum].map((w) => charset[w]).join()}'; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
|
||
| import 'package:mostro/src/rust/api/identity.dart' as identity_api; | ||
| import 'package:mostro/src/rust/api/reputation_transfer.dart' as rust; | ||
|
|
||
| /// The reputation portability calls the screens make, behind one seam so the | ||
| /// widget tests never reach the Rust core. | ||
| class ReputationApi { | ||
| const ReputationApi(); | ||
|
|
||
| rust.ReputationSupportInfo support() => rust.getReputationSupport(); | ||
|
|
||
| rust.ReputationAttestationInfo parse(String json) => | ||
| rust.parseReputationAttestation(json: json); | ||
|
|
||
| Future<rust.ReputationAttestationInfo> import(String json) => | ||
| rust.importReputation(attestationJson: json); | ||
|
|
||
| Future<rust.ReputationAttestationInfo> export({ | ||
| String? destination, | ||
| String? rebind, | ||
| }) => rust.exportReputation(destination: destination, rebind: rebind); | ||
|
|
||
| Future<String> signRebind({ | ||
| required String issuer, | ||
| required String newIdentity, | ||
| }) => rust.signReputationRebind(issuer: issuer, newIdentity: newIdentity); | ||
|
|
||
| Future<rust.ReputationAttestationInfo?> pending() => | ||
| rust.getPendingReputationAttestation(); | ||
|
|
||
| /// The user's identity public key, hex, or null without one. | ||
| Future<String?> identity() async => | ||
| (await identity_api.getIdentity())?.publicKey; | ||
| } | ||
|
|
||
| final reputationApiProvider = Provider<ReputationApi>( | ||
| (ref) => const ReputationApi(), | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import 'package:mostro/core/daemon_errors.dart'; | ||
| import 'package:mostro/l10n/app_localizations.dart'; | ||
|
|
||
| /// User-facing text for a failed reputation export or import: the markers | ||
| /// `api::reputation_transfer` raises, then the shared daemon markers | ||
| /// (`NoDaemonResponse`, node capability), then [fallback]. | ||
| String localizedReputationError( | ||
| AppLocalizations l10n, | ||
| Object error, { | ||
| String? fallback, | ||
| }) { | ||
| final raw = error.toString(); | ||
| final byMarker = <String, String>{ | ||
| 'PrivacyModeEnabled': l10n.reputationIdentityRequired, | ||
| 'ReputationIdentityRequired': l10n.reputationIdentityRequired, | ||
| 'ReputationExportUnsupported': l10n.reputationNodeDoesNotExport, | ||
| 'ReputationImportUnsupported': l10n.reputationNodeDoesNotImport, | ||
| 'NotEligibleForReputationExport': l10n.reputationNotEligible, | ||
| 'ReputationBoundToOtherIdentity': l10n.reputationBoundToOtherIdentity, | ||
| 'InvalidReputationRebind': l10n.reputationInvalidRebind, | ||
| 'ExpiredReputationAttestation': l10n.reputationExpiredAttestation, | ||
| 'UntrustedReputationIssuer': l10n.reputationUntrustedIssuer, | ||
| 'ReputationIdentityMismatch': l10n.reputationIdentityMismatch, | ||
| 'ReputationAlreadyImported': l10n.reputationAlreadyImported, | ||
| }; | ||
| for (final entry in byMarker.entries) { | ||
| if (raw.contains(entry.key)) return entry.value; | ||
| } | ||
| return localizedDaemonError( | ||
| l10n, | ||
| error, | ||
| fallback: fallback ?? l10n.reputationInvalidAttestation, | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.