mirror of
https://github.com/block/buzz.git
synced 2026-11-04 00:12:02 +00:00
perf(mobile): verify owned-agent registrations natively (#8938)
## Summary `loadOwnedAgentDirectory` checks the signature of each kind:30177 agent registration before it trusts the registration's `model`. It did this with `nostr.Event.fromJson`, which runs the `nostr` package's pure-Dart Schnorr code on the UI isolate. This change uses `verifiedNostrEventFromJson`, which the app's other verification sites already use. It makes the same timestamp, event id and BIP-340 signature checks with the bundled libsecp256k1. It also requires lowercase hex for the pubkey and signature, which relay events already use. A registration that fails verification still lists its agent without a model, as before. ## Measurements - **Synthetic** (Mac, `flutter test`, 20 signed registrations, warm): pure Dart 163 ms, native 2.6 ms. - **Device** (iPhone 12 mini, Profile build, first channel open after launch, first 5 s; runs not matched): `loadOwnedAgentDirectory` inclusive CPU was 71 ms before (69 ms in `Event._assertValid`) and 2 ms after. ## Testing - New test: a registration with a wrong signature still lists the agent, with no model. The correctly signed registration keeps its model. - `just mobile-check mobile-test` passes (pre-push hook). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:nostr/nostr.dart' as nostr;
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../crypto/event_signing.dart';
|
||||
import '../crypto/nip_oa.dart';
|
||||
import '../mentions/agent_identity_provider.dart';
|
||||
import '../relay/relay.dart';
|
||||
@@ -99,7 +100,9 @@ Future<List<OwnedAgentDirectoryEntry>> loadOwnedAgentDirectory(
|
||||
String? model;
|
||||
try {
|
||||
final registration = registrations[event.pubkey]!;
|
||||
nostr.Event.fromJson(jsonEncode(registration.toJson()));
|
||||
// Native libsecp256k1 verification; the pure-Dart codec took
|
||||
// several milliseconds per registration on the UI isolate.
|
||||
verifiedNostrEventFromJson(jsonEncode(registration.toJson()));
|
||||
final data = jsonDecode(registration.content);
|
||||
if (data is Map && data['model'] is String) {
|
||||
final value = (data['model'] as String).trim();
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:buzz/shared/relay/relay.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:nostr/nostr.dart' as nostr;
|
||||
|
||||
import '../../helpers/remote_agents_fixture.dart';
|
||||
|
||||
NostrEvent registration(
|
||||
@@ -137,6 +138,40 @@ void main() {
|
||||
]);
|
||||
});
|
||||
|
||||
test('a registration whose signature fails keeps the agent without its '
|
||||
'model', () async {
|
||||
final signed = registration(
|
||||
fakeAgent,
|
||||
content: '{"name":"Remote","model":"Claude Sonnet"}',
|
||||
);
|
||||
// Same id and content, but another key's signature over the id.
|
||||
final forged = NostrEvent.fromJson({
|
||||
...signed.toJson(),
|
||||
'sig': nostr.Event.from(
|
||||
kind: 1,
|
||||
content: 'other',
|
||||
secretKey: fakeAgentKeys.secret,
|
||||
).sig,
|
||||
});
|
||||
final profiles = [
|
||||
signedAgentProfile(
|
||||
fakeAgentKeys,
|
||||
fakeOwner,
|
||||
content: '{"name":"Remote"}',
|
||||
),
|
||||
];
|
||||
Future<List<OwnedAgentDirectoryEntry>> load(NostrEvent event) =>
|
||||
loadOwnedAgentDirectory(
|
||||
fakeOwner.public,
|
||||
(filter) async => filter.kinds.contains(30177) ? [event] : profiles,
|
||||
);
|
||||
|
||||
expect((await load(signed)).single.model, 'Claude Sonnet');
|
||||
final found = (await load(forged)).single;
|
||||
expect(found.pubkey, fakeAgent);
|
||||
expect(found.model, isNull);
|
||||
});
|
||||
|
||||
test('a registration is listed only with a type runtime and a name', () {
|
||||
final agent = fakeAgent;
|
||||
final cases = <(String, NostrEvent, bool)>[
|
||||
|
||||
Reference in New Issue
Block a user