nips/100.md
2023-11-29 11:22:26 -03:00

15 KiB

NIP-100

Android Signer Application

draft optional author:greenart7c3

This NIP describes a method for 2-way communication between a android signer and any Nostr client on Android. The Android signer is an Android Application and the Client can be a Web Client or an Android Application.

Usage for Android applications

The Android signer uses Intents and Content Resolvers to communicate between applications.

To be able to use The Android signer in your application you should add the package name of the signer to your AndroidManifest.xml:

<queries>
    <package android:name="com.example.signer"/>
</queries>

Using Intents

To get the result back from the Signer Appication you should use registerForActivityResult or rememberLauncherForActivityResult in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result.

Create the Intent using the nostrsigner scheme:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$content"))
  • Set the Signer package name
intent.`package` = "com.example.signer"

Methods

  • get_public_key

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:"))
      intent.`package` = "com.example.signer"
      intent.putExtra("type", "get_public_key")
      // You can send some default permissions for the user authorize for ever
      val permissions = listOf(
        Permission(
            "sign_event",
            22242
        ),
        Permission(
            "nip44_decrypt"
        )
      )
      intent.putExtra("permissions", permissions.toJson())
      context.startActivity(intent)
      
    • result:

      • If the user approved intent it will return the npub in the signature field

        val npub = intent.data?.getStringExtra("signature")
        
  • sign_event

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
      intent.`package` = "com.example.signer"
      intent.putExtra("type", "sign_event")
      // to control the result in your application in case you are not waiting the result before sending another intent
      intent.putExtra("id", event.id)
      // Send the current logged in user npub
      intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
      
      context.startActivity(intent)
      
    • result:

      • If the user approved intent it will return the signature, id and event fields

        val signature = intent.data?.getStringExtra("signature")
        // the id you sent
        val id = intent.data?.getStringExtra("id")
        val signedEventJson = intent.data?.getStringExtra("event")
        
  • nip04_encrypt

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$plaintext"))
      intent.`package` = "com.example.signer"
      intent.putExtra("type", "nip04_encrypt")
      // to control the result in your application in case you are not waiting the result before sending another intent
      intent.putExtra("id", "some_id")
      // Send the current logged in user npub
      intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
      // Send the hex pubKey that will be used for encrypting the data
      intent.putExtra("pubKey", pubKey)
      
      context.startActivity(intent)
      
    • result:

      • If the user approved intent it will return the signature and id fields

        val encryptedText = intent.data?.getStringExtra("signature")
        // the id you sent
        val id = intent.data?.getStringExtra("id")
        
  • nip44_encrypt

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$plaintext"))
      intent.`package` = "com.example.signer"
      intent.putExtra("type", "nip44_encrypt")
      // to control the result in your application in case you are not waiting the result before sending another intent
      intent.putExtra("id", "some_id")
      // Send the current logged in user npub
      intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
      // Send the hex pubKey that will be used for encrypting the data
      intent.putExtra("pubKey", pubKey)
      
      context.startActivity(intent)
      
    • result:

      • If the user approved intent it will return the signature and id fields

        val encryptedText = intent.data?.getStringExtra("signature")
        // the id you sent
        val id = intent.data?.getStringExtra("id")
        
  • nip04_decrypt

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText"))
      intent.`package` = "com.example.signer"
      intent.putExtra("type", "nip04_decrypt")
      // to control the result in your application in case you are not waiting the result before sending another intent
      intent.putExtra("id", "some_id")
      // Send the current logged in user npub
      intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
      // Send the hex pubKey that will be used for decrypting the data
      intent.putExtra("pubKey", pubKey)
      
      context.startActivity(intent)
      
    • result:

      • If the user approved intent it will return the signature and id fields

        val plainText = intent.data?.getStringExtra("signature")
        // the id you sent
        val id = intent.data?.getStringExtra("id")
        
  • nip44_decrypt

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText"))
      intent.`package` = "com.example.signer"
      intent.putExtra("type", "nip04_decrypt")
      // to control the result in your application in case you are not waiting the result before sending another intent
      intent.putExtra("id", "some_id")
      // Send the current logged in user npub
      intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
      // Send the hex pubKey that will be used for decrypting the data
      intent.putExtra("pubKey", pubKey)
      
      context.startActivity(intent)
      
    • result:

      • If the user approved intent it will return the signature and id fields

        val plainText = intent.data?.getStringExtra("signature")
        // the id you sent
        val id = intent.data?.getStringExtra("id")
        
  • decrypt_zap_event

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
      intent.`package` = "com.example.signer"
      intent.putExtra("type", "decrypt_zap_event")
      // to control the result in your application in case you are not waiting the result before sending another intent
      intent.putExtra("id", "some_id")
      // Send the current logged in user npub
      intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
      context.startActivity(intent)
      
    • result:

      • If the user approved intent it will return the signature and id fields

        val eventJson = intent.data?.getStringExtra("signature")
        // the id you sent
        val id = intent.data?.getStringExtra("id")
        

Using Content Resolver

To get the result back from Signer Application you should use contentResolver.query in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result.

If the user did not check the remember my choice option, the npub is not in Signer Application or the signer type is not recognized the contentResolver will return null

For the SIGN_EVENT type Signer Application returns two columns "signature" and "event". The column event is the signed event json

For the other types Signer Application returns the column "signature"

Methods

  • get_public_key

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.example.signer.GET_PUBLIC_KEY"),
          listOf("login"),
          null,
          null,
          null
      )
      
    • result:

      • Will return the npub in the signature column

          if (result == null) return
        
          if (result.moveToFirst()) {
              val index = it.getColumnIndex("signature")
              if (index < 0) return
              val npub = it.getString(index)
          }
        
  • sign_event

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.example.signer.SIGN_EVENT"),
          listOf("$eventJson", "", "${logged_in_user_npub}"),
          null,
          null,
          null
      )
      
    • result:

      • Will return the signature and the event columns

          if (result == null) return
        
          if (result.moveToFirst()) {
              val index = it.getColumnIndex("signature")
              val indexJson = it.getColumnIndex("event")
              val signature = it.getString(index)
              val eventJson = it.getString(indexJson)
          }
        
  • nip04_encrypt

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.example.signer.NIP04_ENCRYPT"),
          listOf("$plainText", "${hex_pub_key}", "${logged_in_user_npub}"),
          null,
          null,
          null
      )
      
    • result:

      • Will return the signature column

          if (result == null) return
        
          if (result.moveToFirst()) {
              val index = it.getColumnIndex("signature")
              val encryptedText = it.getString(index)
          }
        
  • nip44_encrypt

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.example.signer.NIP44_ENCRYPT"),
          listOf("$plainText", "${hex_pub_key}", "${logged_in_user_npub}"),
          null,
          null,
          null
      )
      
    • result:

      • Will return the signature column

          if (result == null) return
        
          if (result.moveToFirst()) {
              val index = it.getColumnIndex("signature")
              val encryptedText = it.getString(index)
          }
        
  • nip04_decrypt

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.example.signer.NIP04_DECRYPT"),
          listOf("$encryptedText", "${hex_pub_key}", "${logged_in_user_npub}"),
          null,
          null,
          null
      )
      
    • result:

      • Will return the signature column

          if (result == null) return
        
          if (result.moveToFirst()) {
              val index = it.getColumnIndex("signature")
              val encryptedText = it.getString(index)
          }
        
  • nip44_decrypt

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.example.signer.NIP44_DECRYPT"),
          listOf("$encryptedText", "${hex_pub_key}", "${logged_in_user_npub}"),
          null,
          null,
          null
      )
      
    • result:

      • Will return the signature column

          if (result == null) return
        
          if (result.moveToFirst()) {
              val index = it.getColumnIndex("signature")
              val encryptedText = it.getString(index)
          }
        
  • decrypt_zap_event

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.example.signer.DECRYPT_ZAP_EVENT"),
          listOf("$eventJson", "", "${logged_in_user_npub}"),
          null,
          null,
          null
      )
      
    • result:

      • Will return the signature column

          if (result == null) return
        
          if (result.moveToFirst()) {
              val index = it.getColumnIndex("signature")
              val eventJson = it.getString(index)
          }
        

Usage for Web Applications

Since web applications can't receive a result from the intent you should add a modal to paste the signature or the event json or create a callback url.

If you send the callback url parameter Signer Application will send the result to the url.

If you don't send a callback url Signer Application will copy the result to the clipboard.

You can configure the returnType to be signature or event.

Android intents and browsers url has limitations, so if you are using the returnType of event consider using the parameter compressionType=gzip that will return "Signer1" + Base 64 gzip encoded event json

Methods

  • get_public_key

    • params:

      const intent = `intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;S.callbackUrl=https://example.com/?event=;end`;
      
      window.href = intent;
      
  • sign_event

    • params:

      const intent = `intent:${eventJson}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=sign_event;S.callbackUrl=https://example.com/?event=;end`;
      
      window.href = intent;
      
  • nip04_encrypt

    • params:

      const intent = `intent:${plainText}#Intent;scheme=nostrsigner;S.pubKey=${hex_pub_key};S.compressionType=none;S.returnType=signature;S.type=nip04_encrypt;S.callbackUrl=https://example.com/?event=;end`;
      
      window.href = intent;
      
  • nip44_encrypt

    • params:

      const intent = `intent:${plainText}#Intent;scheme=nostrsigner;S.pubKey=${hex_pub_key};S.compressionType=none;S.returnType=signature;S.type=nip44_encrypt;S.callbackUrl=https://example.com/?event=;end`;
      
      window.href = intent;
      
  • nip04_decrypt

    • params:

      const intent = `intent:${encryptedText}#Intent;scheme=nostrsigner;S.pubKey=${hex_pub_key};S.compressionType=none;S.returnType=signature;S.type=nip44_encrypt;S.callbackUrl=https://example.com/?event=;end`;
      
      window.href = intent;
      
  • nip44_decrypt

    • params:

      const intent = `intent:${encryptedText}#Intent;scheme=nostrsigner;S.pubKey=${hex_pub_key};S.compressionType=none;S.returnType=signature;S.type=nip44_decrypt;S.callbackUrl=https://example.com/?event=;end`;
      
      window.href = intent;
      
  • decrypt_zap_event

    • params:

      const intent = `intent:${eventJson}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=decrypt_zap_event;S.callbackUrl=https://example.com/?event=;end`;
      
      window.href = intent;
      

Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Test</h1>
       
    <script>
        window.onload = function() {
            var url = new URL(window.location.href);
            var params = url.searchParams;
            if (params) {
                var param1 = params.get("event");
                if (param1) alert(param1)
            }
            let json = {
                kind: 1,
                content: "test"
            }
            let encodedJson = encodeURIComponent(JSON.stringify(json))
            var newAnchor = document.createElement("a");
            newAnchor.href = `intent:${encodedJson}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=sign_event;S.callbackUrl=https://example.com/?event=;end`;
            newAnchor.textContent = "Open External Signer";
            document.body.appendChild(newAnchor)
        }
    </script>
</body>
</html>