Git Product home page Git Product logo

amber's Introduction

Amber: Nostr event signer for Android

Amber is a nostr event signer for Android. It allows users to keep their nsec segregated in a single, dedicated app. The goal of Amber is to have your smartphone act as a NIP-46 signing device without any need for servers or additional hardware. "Private keys should be exposed to as few systems as possible as each system adds to the attack surface," as the rationale of said NIP states. In addition to native apps, Amber aims to support all current nostr web applications without requiring any extensions or web servers.

Current Features

  • Offline
  • Use nip-46 or make an addendum in nip-46
  • Improve the ui (currently its showing a text with the raw json of the event)
  • Check if we can use Amber to sign the events of web applications
  • Change the sign button to just copy the signature of the event
  • Use content provider to sign events in background when you checked the remember my choice option on android
  • Support for multiple accounts

Usage for Android applications

Amber uses Intents and Content Resolvers to communicate between applications.

To be able to use Amber in your application you should add the following package visibility needs:

<queries>
    <package android:name="com.greenart7c3.nostrsigner"/>
</queries>

Using Intents

To get the result back from Amber 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 Amber package name
intent.`package` = "com.greenart7c3.nostrsigner"

Methods

  • get_public_key

    • params:

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

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

        val npub = intent.data?.getStringExtra("signature")
        // The package name of the signer application
        val packageName = intent.data?.getStringExtra("package")
  • sign_event

    • params:

      val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
      intent.`package` = "com.greenart7c3.nostrsigner"
      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())
      
      // If you are sending multiple intents without awaiting you can add some intent flags to sign all events without opening multiple screens
      intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
      
      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.greenart7c3.nostrsigner"
      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.greenart7c3.nostrsigner"
      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.greenart7c3.nostrsigner"
      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.greenart7c3.nostrsigner"
      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.greenart7c3.nostrsigner"
      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 Amber 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 amber or the signer type is not recognized the contentResolver will return null

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

For the other types amber returns the column "signature"

Methods

  • get_public_key

    • params:

      val result = context.contentResolver.query(
          Uri.parse("content://com.greenart7c3.nostrsigner.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.greenart7c3.nostrsigner.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.greenart7c3.nostrsigner.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.greenart7c3.nostrsigner.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.greenart7c3.nostrsigner.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.greenart7c3.nostrsigner.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.greenart7c3.nostrsigner.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 Amber will send the result to the url.

If you don't send a callback url Amber 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 amber";
            document.body.appendChild(newAnchor)
        }
    </script>
</body>
</html>

Contributing

Issues and pull requests are very welcome.

Contributors

amber's People

Contributors

greenart7c3 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.