feat: mobile Nostr identity — Amber NIP-55 + nsec fallback (Fixes #29)

- Add NostrIdentityContext with SecureStore-backed nsec storage
  and pure-JS bech32/secp256k1 for nsec→npub derivation; private key
  never enters React state or logs
- Android: NIP-55 Amber deep-link integration (get_public_key +
  sign_event) with install-prompt fallback to Play Store when Amber
  is absent; Android queries manifest entry for com.greenart7c3.nostrsigner
- iOS/both: manual nsec entry stored exclusively in expo-secure-store
- Settings tab (gear icon) added to both NativeTabLayout and
  ClassicTabLayout showing: connected npub (truncated), signing method
  badge, Disconnect button (with confirmation + SecureStore wipe)
- Root layout wrapped with NostrIdentityProvider
- app.json: add expo-secure-store plugin + Android intentFilters for
  mobile://amber-callback deep-link return path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-23 16:24:45 -04:00
parent 5dc71e1257
commit 6433d9172c
7 changed files with 1093 additions and 4 deletions

View File

@@ -25,6 +25,10 @@ function NativeTabLayout() {
<Icon sf={{ default: "list.bullet", selected: "list.bullet.circle.fill" }} />
<Label>Feed</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="settings">
<Icon sf={{ default: "gearshape", selected: "gearshape.fill" }} />
<Label>Settings</Label>
</NativeTabs.Trigger>
</NativeTabs>
);
}
@@ -100,6 +104,18 @@ function ClassicTabLayout() {
),
}}
/>
<Tabs.Screen
name="settings"
options={{
title: "Settings",
tabBarIcon: ({ color, size }) =>
isIOS ? (
<SymbolView name="gearshape" tintColor={color} size={size} />
) : (
<Feather name="settings" size={size} color={color} />
),
}}
/>
</Tabs>
);
}