POWR/docs/technical/nostr/external-signers.md

5.4 KiB

External Signer Integration for POWR (NIP-55)

This document outlines the implementation of external Nostr signer support in POWR, particularly focusing on Amber, a popular NIP-55 compliant signer for Android.

Overview

External signers provide a secure way for users to sign Nostr events without exposing their private keys to applications. This follows the NIP-55 specification, which defines a standard protocol for delegating signing operations to a separate application.

Key advantages:

  • Enhanced security: Private keys never leave the signer app
  • Better key management: Users can use the same identity across multiple applications
  • Reduced risk: Applications don't need to handle sensitive key material

Implementation Components

1. Android Manifest Configuration

In app.json, we've added the necessary configuration to:

  • Allow our app to communicate with external signers using the nostrsigner scheme
  • Expose our app to external signers using the powr scheme
"android": {
  // Existing configuration...
  "intentFilters": [
    {
      "autoVerify": true,
      "action": "VIEW",
      "data": [{ "scheme": "powr" }],
      "category": ["BROWSABLE", "DEFAULT"]
    }
  ],
  "queries": {
    "intent": {
      "action": "VIEW",
      "data": [{ "scheme": "nostrsigner" }],
      "category": ["BROWSABLE"]
    }
  }
}

2. Utility Functions

Created utils/ExternalSignerUtils.ts to provide helpers for:

  • Detecting if compatible external signers are installed
  • Formatting permissions for NIP-55 requests
  • Creating intent parameters for communication with external signers
// Key functions:
isExternalSignerInstalled(): Promise<boolean>
formatPermissions(permissions: NIP55Permission[]): string
createIntentParams(params: NIP55Params): { [key: string]: string }

3. NDK Signer Implementation

Created lib/signers/NDKAmberSigner.ts which implements the NDKSigner interface, including:

export default class NDKAmberSigner implements NDKSigner {
  private pubkey: string;
  private packageName: string;

  constructor(pubkey: string, packageName: string) {
    this.pubkey = pubkey;
    this.packageName = packageName;
  }

  static async requestPublicKey(): Promise<{ pubkey: string, packageName: string }> {
    // Implementation to request a public key from the Amber signer
    // through Android Intent mechanism
  }

  async sign(event: NostrEvent): Promise<SignedEvent> {
    // Implementation to sign an event using the Amber signer
    // through Android Intent mechanism
    
    // Returns the signed event with a valid signature
  }

  getPublicKey(): string {
    return this.pubkey;
  }
}

4. UI Integration

Updated components/sheets/NostrLoginSheet.tsx to:

  • Add a "Sign with Amber" button on Android
  • Check if external signers are available before showing the button
  • Implement the login flow using external signers
// Key components:
const [isExternalSignerAvailable, setIsExternalSignerAvailable] = useState<boolean>(false);

// Check for signer availability on component mount
useEffect(() => {
  async function checkExternalSigner() {
    if (Platform.OS === 'android') {
      const available = await ExternalSignerUtils.isExternalSignerInstalled();
      setIsExternalSignerAvailable(available);
    }
  }
  
  checkExternalSigner();
}, []);

// Handler for Amber login
const handleAmberLogin = async () => {
  try {
    const { pubkey, packageName } = await NDKAmberSigner.requestPublicKey();
    // Create an NDKAmberSigner with the public key and package name
    // Set this signer on the NDK instance
    // Update authentication state
  } catch (err) {
    // Error handling
  }
};

Technical Flow

  1. Detection: App checks if external signers are installed on the device
  2. Login Initiation: User taps "Sign with Amber" button
  3. Public Key Request: App creates an Android Intent with action=VIEW and scheme=nostrsigner
  4. Signer Response: Amber responds with the user's public key via a deep link back to the app
  5. Event Signing: For each event that needs signing:
    • App creates an Intent with the event data
    • Amber presents signing request to user
    • Upon approval, Amber signs the event and returns it to the app
    • App processes the signed event and sends it to relays

Security Considerations

  • Always verify signatures on returned events
  • Set appropriate permissions when requesting signing capabilities
  • Implement proper error handling for cases when the external signer is unavailable
  • Clear cached signing references when a user logs out

Testing

To test external signer integration:

  1. Install Amber from the Google Play Store
  2. Create a Nostr identity in Amber
  3. Launch POWR and select "Sign with Amber" on the login screen
  4. Verify that Amber opens and requests authorization
  5. Confirm that events created in POWR are properly signed by Amber

Future Improvements

  • Support for additional NIP-55 compliant signers
  • iOS support when NIP-55 compliant signers become available
  • Enhanced permission management for different event kinds
  • Improved error handling and fallback mechanisms

References