upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/55.md
diff options
context:
space:
mode:
authorVitor Pamplona <vitor@vitorpamplona.com>2024-12-19 10:15:46 -0500
committerGitHub <noreply@github.com>2024-12-19 10:15:46 -0500
commitf7d97f3f40dc6677e6f996ffff431142ffe05810 (patch)
tree2eaa8c8628cc9a4b05811ed3869e21221fd85307 /55.md
parentd5b77b6d7348061cd0f16adc35e4d02a3a0b7380 (diff)
parent561059ff85c171b87a12b8381b724b4afc569a97 (diff)
Merge branch 'master' into draft-event
Diffstat (limited to '55.md')
-rw-r--r--55.md623
1 files changed, 623 insertions, 0 deletions
diff --git a/55.md b/55.md
new file mode 100644
index 0000000..afca0aa
--- /dev/null
+++ b/55.md
@@ -0,0 +1,623 @@
1NIP-55
2======
3
4Android Signer Application
5--------------------------
6
7`draft` `optional`
8
9This NIP describes a method for 2-way communication between an 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.
10
11# Usage for Android applications
12
13The Android signer uses Intents and Content Resolvers to communicate between applications.
14
15To be able to use the Android signer in your application you should add this to your AndroidManifest.xml:
16
17```xml
18<queries>
19 <intent>
20 <action android:name="android.intent.action.VIEW" />
21 <category android:name="android.intent.category.BROWSABLE" />
22 <data android:scheme="nostrsigner" />
23 </intent>
24</queries>
25```
26
27Then you can use this function to check if there's a signer application installed:
28
29```kotlin
30fun isExternalSignerInstalled(context: Context): Boolean {
31 val intent =
32 Intent().apply {
33 action = Intent.ACTION_VIEW
34 data = Uri.parse("nostrsigner:")
35 }
36 val infos = context.packageManager.queryIntentActivities(intent, 0)
37 return infos.size > 0
38}
39```
40
41## Using Intents
42
43To get the result back from the Signer Application 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.
44
45```kotlin
46val launcher = rememberLauncherForActivityResult(
47 contract = ActivityResultContracts.StartActivityForResult(),
48 onResult = { result ->
49 if (result.resultCode != Activity.RESULT_OK) {
50 Toast.makeText(
51 context,
52 "Sign request rejected",
53 Toast.LENGTH_SHORT
54 ).show()
55 } else {
56 val result = activityResult.data?.getStringExtra("result")
57 // Do something with result ...
58 }
59 }
60)
61```
62
63Create the Intent using the **nostrsigner** scheme:
64
65```kotlin
66val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$content"))
67```
68
69Set the Signer package name:
70
71```kotlin
72intent.`package` = "com.example.signer"
73```
74
75If you are sending multiple intents without awaiting you can add some intent flags to sign all events without opening multiple times the signer
76
77```kotlin
78intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
79```
80
81If you are developing a signer application them you need to add this to your AndroidManifest.xml so clients can use the intent flags above
82
83```kotlin
84android:launchMode="singleTop"
85```
86
87Signer MUST answer multiple permissions with an array of results
88
89```kotlin
90
91val results = listOf(
92 Result(
93 package = signerPackageName,
94 result = eventSignture,
95 id = intentId
96 )
97)
98
99val json = results.toJson()
100
101intent.putExtra("results", json)
102```
103
104Send the Intent:
105
106```kotlin
107launcher.launch(intent)
108```
109
110### Methods
111
112- **get_public_key**
113 - params:
114
115 ```kotlin
116 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:"))
117 intent.`package` = "com.example.signer"
118 intent.putExtra("type", "get_public_key")
119 // You can send some default permissions for the user to authorize for ever
120 val permissions = listOf(
121 Permission(
122 type = "sign_event",
123 kind = 22242
124 ),
125 Permission(
126 type = "nip44_decrypt"
127 )
128 )
129 intent.putExtra("permissions", permissions.toJson())
130 context.startActivity(intent)
131 ```
132 - result:
133 - If the user approved intent it will return the **pubkey** in the result field
134
135 ```kotlin
136 val pubkey = intent.data?.getStringExtra("result")
137 // The package name of the signer application
138 val packageName = intent.data?.getStringExtra("package")
139 ```
140
141- **sign_event**
142 - params:
143
144 ```kotlin
145 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
146 intent.`package` = "com.example.signer"
147 intent.putExtra("type", "sign_event")
148 // To handle results when not waiting between intents
149 intent.putExtra("id", event.id)
150 // Send the current logged in user pubkey
151 intent.putExtra("current_user", pubkey)
152
153 context.startActivity(intent)
154 ```
155 - result:
156 - If the user approved intent it will return the **result**, **id** and **event** fields
157
158 ```kotlin
159 val signature = intent.data?.getStringExtra("result")
160 // The id you sent
161 val id = intent.data?.getStringExtra("id")
162 val signedEventJson = intent.data?.getStringExtra("event")
163 ```
164
165- **nip04_encrypt**
166 - params:
167
168 ```kotlin
169 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$plaintext"))
170 intent.`package` = "com.example.signer"
171 intent.putExtra("type", "nip04_encrypt")
172 // to control the result in your application in case you are not waiting the result before sending another intent
173 intent.putExtra("id", "some_id")
174 // Send the current logged in user pubkey
175 intent.putExtra("current_user", account.keyPair.pubkey)
176 // Send the hex pubkey that will be used for encrypting the data
177 intent.putExtra("pubkey", pubkey)
178
179 context.startActivity(intent)
180 ```
181 - result:
182 - If the user approved intent it will return the **result** and **id** fields
183
184 ```kotlin
185 val encryptedText = intent.data?.getStringExtra("result")
186 // the id you sent
187 val id = intent.data?.getStringExtra("id")
188 ```
189
190- **nip44_encrypt**
191 - params:
192
193 ```kotlin
194 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$plaintext"))
195 intent.`package` = "com.example.signer"
196 intent.putExtra("type", "nip44_encrypt")
197 // to control the result in your application in case you are not waiting the result before sending another intent
198 intent.putExtra("id", "some_id")
199 // Send the current logged in user pubkey
200 intent.putExtra("current_user", account.keyPair.pubkey)
201 // Send the hex pubkey that will be used for encrypting the data
202 intent.putExtra("pubkey", pubkey)
203
204 context.startActivity(intent)
205 ```
206 - result:
207 - If the user approved intent it will return the **signature** and **id** fields
208
209 ```kotlin
210 val encryptedText = intent.data?.getStringExtra("signature")
211 // the id you sent
212 val id = intent.data?.getStringExtra("id")
213 ```
214
215- **nip04_decrypt**
216 - params:
217
218 ```kotlin
219 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText"))
220 intent.`package` = "com.example.signer"
221 intent.putExtra("type", "nip04_decrypt")
222 // to control the result in your application in case you are not waiting the result before sending another intent
223 intent.putExtra("id", "some_id")
224 // Send the current logged in user pubkey
225 intent.putExtra("current_user", account.keyPair.pubkey)
226 // Send the hex pubkey that will be used for decrypting the data
227 intent.putExtra("pubkey", pubkey)
228
229 context.startActivity(intent)
230 ```
231 - result:
232 - If the user approved intent it will return the **result** and **id** fields
233
234 ```kotlin
235 val plainText = intent.data?.getStringExtra("result")
236 // the id you sent
237 val id = intent.data?.getStringExtra("id")
238 ```
239
240- **nip44_decrypt**
241 - params:
242
243 ```kotlin
244 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText"))
245 intent.`package` = "com.example.signer"
246 intent.putExtra("type", "nip04_decrypt")
247 // to control the result in your application in case you are not waiting the result before sending another intent
248 intent.putExtra("id", "some_id")
249 // Send the current logged in user pubkey
250 intent.putExtra("current_user", account.keyPair.pubkey)
251 // Send the hex pubkey that will be used for decrypting the data
252 intent.putExtra("pubkey", pubkey)
253
254 context.startActivity(intent)
255 ```
256 - result:
257 - If the user approved intent it will return the **result** and **id** fields
258
259 ```kotlin
260 val plainText = intent.data?.getStringExtra("result")
261 // the id you sent
262 val id = intent.data?.getStringExtra("id")
263 ```
264
265- **get_relays**
266 - params:
267
268 ```kotlin
269 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:"))
270 intent.`package` = "com.example.signer"
271 intent.putExtra("type", "get_relays")
272 // to control the result in your application in case you are not waiting the result before sending another intent
273 intent.putExtra("id", "some_id")
274 // Send the current logged in user pubkey
275 intent.putExtra("current_user", account.keyPair.pubkey)
276
277 context.startActivity(intent)
278 ```
279 - result:
280 - If the user approved intent it will return the **result** and **id** fields
281
282 ```kotlin
283 val relayJsonText = intent.data?.getStringExtra("result")
284 // the id you sent
285 val id = intent.data?.getStringExtra("id")
286 ```
287
288- **decrypt_zap_event**
289 - params:
290
291 ```kotlin
292 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
293 intent.`package` = "com.example.signer"
294 intent.putExtra("type", "decrypt_zap_event")
295 // to control the result in your application in case you are not waiting the result before sending another intent
296 intent.putExtra("id", "some_id")
297 // Send the current logged in user pubkey
298 intent.putExtra("current_user", account.keyPair.pubkey)
299 context.startActivity(intent)
300 ```
301 - result:
302 - If the user approved intent it will return the **result** and **id** fields
303
304 ```kotlin
305 val eventJson = intent.data?.getStringExtra("result")
306 // the id you sent
307 val id = intent.data?.getStringExtra("id")
308 ```
309
310## Using Content Resolver
311
312To 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.
313
314If the user did not check the "remember my choice" option, the pubkey is not in Signer Application or the signer type is not recognized the `contentResolver` will return null
315
316For the SIGN_EVENT type Signer Application returns two columns "result" and "event". The column event is the signed event json
317
318For the other types Signer Application returns the column "result"
319
320If the user chose to always reject the event, signer application will return the column "rejected" and you should not open signer application
321
322### Methods
323
324- **get_public_key**
325 - params:
326
327 ```kotlin
328 val result = context.contentResolver.query(
329 Uri.parse("content://com.example.signer.GET_PUBLIC_KEY"),
330 listOf("login"),
331 null,
332 null,
333 null
334 )
335 ```
336 - result:
337 - Will return the **pubkey** in the result column
338
339 ```kotlin
340 if (result == null) return
341
342 if (result.moveToFirst()) {
343 val index = it.getColumnIndex("result")
344 if (index < 0) return
345 val pubkey = it.getString(index)
346 }
347 ```
348
349- **sign_event**
350 - params:
351
352 ```kotlin
353 val result = context.contentResolver.query(
354 Uri.parse("content://com.example.signer.SIGN_EVENT"),
355 listOf("$eventJson", "", "${logged_in_user_pubkey}"),
356 null,
357 null,
358 null
359 )
360 ```
361 - result:
362 - Will return the **result** and the **event** columns
363
364 ```kotlin
365 if (result == null) return
366
367 if (result.moveToFirst()) {
368 val index = it.getColumnIndex("result")
369 val indexJson = it.getColumnIndex("event")
370 val signature = it.getString(index)
371 val eventJson = it.getString(indexJson)
372 }
373 ```
374
375- **nip04_encrypt**
376 - params:
377
378 ```kotlin
379 val result = context.contentResolver.query(
380 Uri.parse("content://com.example.signer.NIP04_ENCRYPT"),
381 listOf("$plainText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
382 null,
383 null,
384 null
385 )
386 ```
387 - result:
388 - Will return the **result** column
389
390 ```kotlin
391 if (result == null) return
392
393 if (result.moveToFirst()) {
394 val index = it.getColumnIndex("result")
395 val encryptedText = it.getString(index)
396 }
397 ```
398
399- **nip44_encrypt**
400 - params:
401
402 ```kotlin
403 val result = context.contentResolver.query(
404 Uri.parse("content://com.example.signer.NIP44_ENCRYPT"),
405 listOf("$plainText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
406 null,
407 null,
408 null
409 )
410 ```
411 - result:
412 - Will return the **result** column
413
414 ```kotlin
415 if (result == null) return
416
417 if (result.moveToFirst()) {
418 val index = it.getColumnIndex("result")
419 val encryptedText = it.getString(index)
420 }
421 ```
422
423- **nip04_decrypt**
424 - params:
425
426 ```kotlin
427 val result = context.contentResolver.query(
428 Uri.parse("content://com.example.signer.NIP04_DECRYPT"),
429 listOf("$encryptedText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
430 null,
431 null,
432 null
433 )
434 ```
435 - result:
436 - Will return the **result** column
437
438 ```kotlin
439 if (result == null) return
440
441 if (result.moveToFirst()) {
442 val index = it.getColumnIndex("result")
443 val encryptedText = it.getString(index)
444 }
445 ```
446
447- **nip44_decrypt**
448 - params:
449
450 ```kotlin
451 val result = context.contentResolver.query(
452 Uri.parse("content://com.example.signer.NIP44_DECRYPT"),
453 listOf("$encryptedText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
454 null,
455 null,
456 null
457 )
458 ```
459 - result:
460 - Will return the **result** column
461
462 ```kotlin
463 if (result == null) return
464
465 if (result.moveToFirst()) {
466 val index = it.getColumnIndex("result")
467 val encryptedText = it.getString(index)
468 }
469 ```
470
471- **get_relays**
472 - params:
473
474 ```kotlin
475 val result = context.contentResolver.query(
476 Uri.parse("content://com.example.signer.GET_RELAYS"),
477 listOf("${logged_in_user_pubkey}"),
478 null,
479 null,
480 null
481 )
482 ```
483 - result:
484 - Will return the **result** column
485
486 ```kotlin
487 if (result == null) return
488
489 if (result.moveToFirst()) {
490 val index = it.getColumnIndex("result")
491 val relayJsonText = it.getString(index)
492 }
493 ```
494
495- **decrypt_zap_event**
496 - params:
497
498 ```kotlin
499 val result = context.contentResolver.query(
500 Uri.parse("content://com.example.signer.DECRYPT_ZAP_EVENT"),
501 listOf("$eventJson", "", "${logged_in_user_pubkey}"),
502 null,
503 null,
504 null
505 )
506 ```
507 - result:
508 - Will return the **result** column
509
510 ```kotlin
511 if (result == null) return
512
513 if (result.moveToFirst()) {
514 val index = it.getColumnIndex("result")
515 val eventJson = it.getString(index)
516 }
517 ```
518
519# Usage for Web Applications
520
521Since 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.
522
523If you send the callback url parameter, Signer Application will send the result to the url.
524
525If you don't send a callback url, Signer Application will copy the result to the clipboard.
526
527You can configure the `returnType` to be **signature** or **event**.
528
529Android intents and browser urls have limitations, so if you are using the `returnType` of **event** consider using the parameter **compressionType=gzip** that will return "Signer1" + Base64 gzip encoded event json
530
531## Methods
532
533- **get_public_key**
534 - params:
535
536 ```js
537 window.href = `nostrsigner:?compressionType=none&returnType=signature&type=get_public_key&callbackUrl=https://example.com/?event=`;
538 ```
539
540- **sign_event**
541 - params:
542
543 ```js
544 window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
545 ```
546
547- **nip04_encrypt**
548 - params:
549
550 ```js
551 window.href = `nostrsigner:${plainText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip04_encrypt&callbackUrl=https://example.com/?event=`;
552 ```
553
554- **nip44_encrypt**
555 - params:
556
557 ```js
558 window.href = `nostrsigner:${plainText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip44_encrypt&callbackUrl=https://example.com/?event=`;
559 ```
560
561- **nip04_decrypt**
562 - params:
563
564 ```js
565 window.href = `nostrsigner:${encryptedText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip04_decrypt&callbackUrl=https://example.com/?event=`;
566 ```
567
568- **nip44_decrypt**
569 - params:
570
571 ```js
572 window.href = `nostrsigner:${encryptedText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip44_decrypt&callbackUrl=https://example.com/?event=`;
573 ```
574
575- **get_relays**
576 - params:
577
578 ```js
579 window.href = `nostrsigner:?compressionType=none&returnType=signature&type=get_relays&callbackUrl=https://example.com/?event=`;
580 ```
581
582- **decrypt_zap_event**
583 - params:
584
585 ```js
586 window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=decrypt_zap_event&callbackUrl=https://example.com/?event=`;
587 ```
588
589## Example
590
591```js
592<!DOCTYPE html>
593<html lang="en">
594<head>
595 <meta charset="UTF-8">
596 <meta name="viewport" content="width=device-width, initial-scale=1.0">
597 <title>Document</title>
598</head>
599<body>
600 <h1>Test</h1>
601
602 <script>
603 window.onload = function() {
604 var url = new URL(window.location.href);
605 var params = url.searchParams;
606 if (params) {
607 var param1 = params.get("event");
608 if (param1) alert(param1)
609 }
610 let json = {
611 kind: 1,
612 content: "test"
613 }
614 let encodedJson = encodeURIComponent(JSON.stringify(json))
615 var newAnchor = document.createElement("a");
616 newAnchor.href = `nostrsigner:${encodedJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
617 newAnchor.textContent = "Open External Signer";
618 document.body.appendChild(newAnchor)
619 }
620 </script>
621</body>
622</html>
623```