The global membership rule (curated bootstrap referents + sigQty/stepMax parameters) solved sybil-proof identity for a UBI — a problem this app doesn't have — and its screen leaked graph jargon (npub roots, parameter steppers). Trust is now computed from the user's own position only: you vouch / vouched by people you know (distance <=2) / vouched by N. - TrustCubit: drop networkMember tier, referents and params; keep the circle rule and the 365-day vouch expiry (renewable, self-pruning). - Delete TrustReferents, WotSettings, TrustNetworkScreen and the bundled referents asset; unwire injector/bootstrap/app/chat. - New 'Your people' screen (/your-people, from the profile): who you vouch for (revocable) and who vouches for you, names via ProfileCache. - i18n: wot.* removed, yourPeople.* added (en/es/pt/ast); trust.member removed. Kind 30777 events on relays stay fully compatible — only the interpretation changes.
154 lines
4.5 KiB
Dart
154 lines
4.5 KiB
Dart
import 'package:commons_core/commons_core.dart';
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
/// Where a peer stands, from strongest to weakest signal. Trust is
|
|
/// ego-centric: computed from YOUR position in the public vouch graph,
|
|
/// never from a global membership verdict.
|
|
enum TrustTier {
|
|
/// In your personal circle (you vouch, or a friend-of-a-friend does).
|
|
inYourCircle,
|
|
|
|
/// Vouched for by someone, but not (yet) in your circle.
|
|
vouched,
|
|
|
|
/// No certifications seen.
|
|
unknown,
|
|
}
|
|
|
|
/// Trust standing of one peer, seen from this user's position: your own
|
|
/// vouch, your circle (friend-of-a-friend), and the raw certifier count —
|
|
/// computed from the public certification graph.
|
|
class TrustState extends Equatable {
|
|
const TrustState({
|
|
this.certifierCount = 0,
|
|
this.iVouch = false,
|
|
this.knownToYou = false,
|
|
this.loading = true,
|
|
this.busy = false,
|
|
});
|
|
|
|
/// How many people (network-wide) currently vouch for the peer.
|
|
final int certifierCount;
|
|
|
|
/// Whether this user vouches for the peer.
|
|
final bool iVouch;
|
|
|
|
/// Whether the peer is within the user's own circle (you, or a
|
|
/// friend-of-a-friend, vouch). Spam-resistant and works from day one.
|
|
final bool knownToYou;
|
|
|
|
final bool loading;
|
|
final bool busy;
|
|
|
|
/// The strongest applicable signal, for the badge.
|
|
TrustTier get tier {
|
|
if (knownToYou) return TrustTier.inYourCircle;
|
|
if (certifierCount > 0) return TrustTier.vouched;
|
|
return TrustTier.unknown;
|
|
}
|
|
|
|
TrustState copyWith({
|
|
int? certifierCount,
|
|
bool? iVouch,
|
|
bool? knownToYou,
|
|
bool? loading,
|
|
bool? busy,
|
|
}) =>
|
|
TrustState(
|
|
certifierCount: certifierCount ?? this.certifierCount,
|
|
iVouch: iVouch ?? this.iVouch,
|
|
knownToYou: knownToYou ?? this.knownToYou,
|
|
loading: loading ?? this.loading,
|
|
busy: busy ?? this.busy,
|
|
);
|
|
|
|
@override
|
|
List<Object?> get props => [
|
|
certifierCount,
|
|
iVouch,
|
|
knownToYou,
|
|
loading,
|
|
busy,
|
|
];
|
|
}
|
|
|
|
/// Reads and toggles this user's vouch for [peerPubkey] over a
|
|
/// [TrustTransport], and computes the peer's standing from this user's own
|
|
/// position in the vouch graph (ego-centric — no referents, no parameters).
|
|
class TrustCubit extends Cubit<TrustState> {
|
|
TrustCubit(
|
|
this._transport, {
|
|
required this.peerPubkey,
|
|
required this.selfPubkey,
|
|
Future<void> Function()? onDispose,
|
|
}) : _onDispose = onDispose,
|
|
super(const TrustState());
|
|
|
|
final TrustTransport? _transport;
|
|
final String peerPubkey;
|
|
final String selfPubkey;
|
|
final Future<void> Function()? _onDispose;
|
|
|
|
bool get isOnline => _transport != null;
|
|
|
|
/// Personal "circle" rule: one vouch from your side, out to a friend-of-a-
|
|
/// friend. Loose by design — "people near you".
|
|
static const _circleThreshold = 1;
|
|
static const _circleDistance = 2;
|
|
|
|
/// Vouches expire and must be renewed, so stale trust prunes itself.
|
|
static const _vouchValidity = Duration(days: 365);
|
|
|
|
/// Loads the certification graph and computes: the peer's certifier count,
|
|
/// whether you vouch, and whether they're in your circle.
|
|
Future<void> load() async {
|
|
final transport = _transport;
|
|
if (transport == null) {
|
|
emit(state.copyWith(loading: false));
|
|
return;
|
|
}
|
|
emit(state.copyWith(loading: true));
|
|
final wot = WebOfTrust.fromCertifications(
|
|
await transport.allCertifications(),
|
|
now: DateTime.now(),
|
|
);
|
|
final certifiers = wot.certifiersOf(peerPubkey);
|
|
final circle = wot.members(
|
|
seeds: {selfPubkey},
|
|
threshold: _circleThreshold,
|
|
maxDistance: _circleDistance,
|
|
);
|
|
emit(state.copyWith(
|
|
certifierCount: certifiers.length,
|
|
iVouch: certifiers.contains(selfPubkey),
|
|
knownToYou: circle.contains(peerPubkey),
|
|
loading: false,
|
|
));
|
|
}
|
|
|
|
/// Adds or removes this user's vouch, then reloads. Never vouches for self.
|
|
/// The certification is issued with a validity so it expires and must be
|
|
/// renewed.
|
|
Future<void> toggleVouch() async {
|
|
final transport = _transport;
|
|
if (transport == null || peerPubkey == selfPubkey) return;
|
|
emit(state.copyWith(busy: true));
|
|
if (state.iVouch) {
|
|
await transport.revoke(subjectPubkey: peerPubkey);
|
|
} else {
|
|
await transport.certify(
|
|
subjectPubkey: peerPubkey,
|
|
validity: _vouchValidity,
|
|
);
|
|
}
|
|
await load();
|
|
emit(state.copyWith(busy: false));
|
|
}
|
|
|
|
@override
|
|
Future<void> close() async {
|
|
await _onDispose?.call();
|
|
return super.close();
|
|
}
|
|
}
|