mirror of
https://github.com/block/buzz.git
synced 2026-10-30 14:01:37 +00:00
Mobile signs every Nostr event (sends, relay AUTH, media auth headers, preference writes) in pure Dart on the UI isolate. On an iPhone SE (2nd gen, Profile build) one BIP-340 signature costs about 10.7 ms and deriving the public key another 5.6 ms, so a burst of signed writes drops frames. This adds `mobile/packages/buzz_secp256k1`, a small FFI package that builds vendored bitcoin-core/libsecp256k1 v0.8.0 (ecdh, extrakeys, schnorrsig modules) through a Dart build hook, so the same C and the same Dart API ship on iOS and Android with no platform bridge code. All 13 `nostr.Event.from` signing sites now go through `signNostrEvent`, which keeps the `nostr` package's event-id serialization and moves only key derivation and signing to native code. Calls stay synchronous. The package also exposes `schnorrVerify` and `ecdhX`; follow-up PRs move verification and the NIP-44 conversation key over. Incoming relay events are still not verified, unchanged by this PR. Safety: the signing context is randomized once per isolate, secret keys and keypairs are wiped from the native scratch buffer after each call, aux randomness comes from `Random.secure()`, and the static verify context runs libsecp256k1's self test before first use. ### Validation - Added tests: BIP-340 official vectors and native-vs-pure-Dart differential tests in the package; `event_signing_test.dart` signs with 64 random keys and checks pubkey, event id and signature against the existing `nostr` builder and verifier, plus malformed-key rejection. - Full mobile analyze and test suite pass locally. - Builds: iOS release (framework ~1.3 MB) and Android debug APK with the library for arm64-v8a, armeabi-v7a and x86_64 (~1.2–1.3 MB each). - On-device (iPhone SE 2nd gen, Profile, median per op): sign 10,736 µs → 41 µs, derive pubkey 5,580 µs → 16.5 µs, full signed event 10,744 µs → 65 µs. Event-id JSON and SHA-256 are 23 µs, so they stay in Dart. - Device, iOS (iPhone SE 2nd gen, Profile): `buzz_secp256k1.framework` embedded; the signed-in app authenticated to a real community relay, loaded channels and history, and paged older messages. No native-asset or secp256k1 errors in the console. - Device, Android (emulator, debug): APK contains `libbuzz_secp256k1.so` and loads it. Against a local mock relay that verifies with the pure-Dart `nostr` package, the app's NIP-42 AUTH, NIP-98 `POST /query` headers, and kind 20001/30078 events all verified. - Blossom GET auth, iOS: the dev relay returns 401 for unauthenticated `/media/` GETs. From inside the running app, the production `MediaGetAuthService.headersFor()` header for a nonexistent hash got 404 (accepted, file missing), compared with 401 without it. The community has no relay-hosted image, so none was rendered. buzz-review-completed (agent review, Codex and security review clean; device validation above; human app test not applicable under the mobile testing exception) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
2.5 KiB
YAML
98 lines
2.5 KiB
YAML
name: buzz
|
|
description: Buzz mobile client
|
|
publish_to: 'none'
|
|
version: 0.0.0+1
|
|
|
|
environment:
|
|
sdk: ^3.11.4
|
|
flutter: ">=3.47.7"
|
|
|
|
dependencies:
|
|
flutter:
|
|
sdk: flutter
|
|
cupertino_icons: ^1.0.8
|
|
# Grapheme-cluster segmentation, for deciding whether a message body is
|
|
# nothing but emoji. Already present transitively via the Flutter SDK;
|
|
# declared here because we import it directly.
|
|
characters: ^1.4.0
|
|
hooks_riverpod: ^3.0.3
|
|
flutter_hooks: ^0.21.3
|
|
http: ^1.4.0
|
|
crypto: ^3.0.7
|
|
flutter_secure_storage: ^10.0.0
|
|
flutter_svg: ^2.2.4
|
|
mobile_scanner: ^7.0.0
|
|
shared_preferences: ^2.5.5
|
|
web_socket_channel: ^3.0.1
|
|
connectivity_plus: ^7.0.0
|
|
nostr: ^2.0.0
|
|
buzz_secp256k1:
|
|
path: packages/buzz_secp256k1
|
|
pointycastle: ^4.0.0
|
|
url_launcher: ^6.3.2
|
|
gpt_markdown: ^1.2.1
|
|
highlight: ^0.7.0
|
|
intl: ^0.20.2
|
|
uuid: ^4.5.1
|
|
file_selector: ^1.1.0
|
|
camera: ^0.12.0+2
|
|
image_picker: ^1.1.2
|
|
image: ^4.8.0
|
|
google_mlkit_selfie_segmentation: ^0.11.0
|
|
photo_manager: ^3.11.0
|
|
video_player: ^2.10.1
|
|
just_audio: ^0.10.6
|
|
record: ^6.2.1
|
|
package_info_plus: ^10.0.0
|
|
app_badge_plus: ^1.2.10
|
|
app_links: ^6.4.0
|
|
scrollable_positioned_list: ^0.3.8
|
|
open_filex: ^4.7.0
|
|
path_provider: ^2.1.6
|
|
share_plus: ^13.3.0
|
|
local_auth: ^3.0.2
|
|
|
|
dev_dependencies:
|
|
leak_tracker: ^11.0.2
|
|
shared_preferences_platform_interface: ^2.4.2
|
|
flutter_test:
|
|
sdk: flutter
|
|
flutter_lints: ^6.0.0
|
|
fake_async: ^1.3.3
|
|
camera_platform_interface: ^2.13.1
|
|
custom_lint: ^0.8.0
|
|
riverpod_lint: ^3.1.0
|
|
mocktail: ^1.0.4
|
|
# Test-only platform interface fakes (depend_on_referenced_packages).
|
|
path_provider_platform_interface: ^2.1.2
|
|
plugin_platform_interface: ^2.1.8
|
|
video_player_platform_interface: ^6.6.0
|
|
|
|
flutter:
|
|
config:
|
|
enable-swift-package-manager: false
|
|
uses-material-design: true
|
|
shaders:
|
|
- shaders/onboarding_wordmark.frag
|
|
assets:
|
|
- assets/images/buzz-icon.png
|
|
- assets/images/shell-gradient.png
|
|
- assets/images/buzz-wordmark.png
|
|
# Generated by `just mobile-emoji-data` from the same emoji-mart dataset
|
|
# desktop uses. Do not hand-edit.
|
|
- assets/emoji/emoji-data.json
|
|
fonts:
|
|
- family: BuzzTabler
|
|
fonts:
|
|
- asset: assets/fonts/TablerIcons.ttf
|
|
- family: Inter
|
|
fonts:
|
|
- asset: assets/fonts/InterVariable.ttf
|
|
- asset: assets/fonts/InterVariable-Italic.ttf
|
|
style: italic
|
|
- family: GeistMono
|
|
fonts:
|
|
- asset: assets/fonts/GeistMono-Variable.ttf
|
|
- asset: assets/fonts/GeistMono-Italic-Variable.ttf
|
|
style: italic
|