pakstr 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/android-template/app/build.gradle.kts +4 -0
  3. package/android-template/app/src/androidTest/java/com/pakstr/app/signer/SignerManagerTest.kt +273 -0
  4. package/android-template/app/src/main/assets/www/index.html +0 -6
  5. package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +20 -4
  6. package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +130 -12
  7. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Hash.kt +56 -0
  8. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Signer.kt +104 -0
  9. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Verifier.kt +20 -170
  10. package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip44Crypto.kt +513 -0
  11. package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip46ClientKey.kt +140 -0
  12. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventHasher.kt +3 -28
  13. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventSerializer.kt +74 -0
  14. package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +100 -0
  15. package/android-template/app/src/main/java/com/pakstr/app/signer/BunkerSigner.kt +32 -7
  16. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +36 -9
  17. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +240 -163
  18. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/BunkerConfig.kt +175 -0
  19. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/BunkerConnectionStore.kt +144 -0
  20. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Client.kt +79 -0
  21. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Models.kt +319 -0
  22. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingClient.kt +159 -0
  23. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingQrCode.kt +35 -0
  24. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingUri.kt +104 -0
  25. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Protocol.kt +695 -0
  26. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46RelayTransport.kt +489 -0
  27. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/RelayNip46Client.kt +403 -0
  28. package/android-template/app/src/main/java/com/pakstr/app/utils/BunkerPairingDialog.kt +82 -0
  29. package/android-template/app/src/main/java/com/pakstr/app/utils/SignerPickerBottomSheet.kt +78 -17
  30. package/android-template/app/src/main/res/layout/activity_layout.xml +1 -0
  31. package/android-template/app/src/main/res/layout/dialog_bunker_pairing.xml +75 -0
  32. package/android-template/app/src/main/res/layout/dialog_picker.xml +60 -31
  33. package/android-template/app/src/main/res/values/strings.xml +27 -0
  34. package/android-template/app/src/test/java/com/pakstr/app/LocalServerQueryKeepAliveTest.kt +118 -0
  35. package/android-template/app/src/test/java/com/pakstr/app/crypto/Bip340SignerTest.kt +133 -0
  36. package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip44CryptoTest.kt +199 -0
  37. package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip46ClientKeyProtocolTest.kt +82 -0
  38. package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip46ClientKeyTest.kt +52 -0
  39. package/android-template/app/src/test/java/com/pakstr/app/crypto/NostrEventSerializerTest.kt +42 -0
  40. package/android-template/app/src/test/java/com/pakstr/app/signer/NostrBridgeErrorTest.kt +32 -0
  41. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/BunkerConfigTest.kt +120 -0
  42. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46ModelsTest.kt +111 -0
  43. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingClientTest.kt +271 -0
  44. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingQrCodeTest.kt +54 -0
  45. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingUriTest.kt +80 -0
  46. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46ProtocolTest.kt +578 -0
  47. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46RelayTransportTest.kt +508 -0
  48. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/RelayNip46ClientTest.kt +678 -0
  49. package/android-template/gradle/libs.versions.toml +8 -0
  50. package/package.json +1 -1
@@ -0,0 +1,74 @@
1
+ package com.pakstr.app.crypto
2
+
3
+ import org.json.JSONArray
4
+ import org.json.JSONObject
5
+
6
+ internal object NostrEventSerializer {
7
+ fun serialize(event: JSONObject): String {
8
+ val output = StringBuilder()
9
+ output.append("[0,")
10
+ appendString(output, event.getString("pubkey"))
11
+ output.append(',')
12
+ output.append(event.getLong("created_at"))
13
+ output.append(',')
14
+ output.append(event.getInt("kind"))
15
+ output.append(',')
16
+ appendTags(output, event.getJSONArray("tags"))
17
+ output.append(',')
18
+ appendString(output, event.getString("content"))
19
+ output.append(']')
20
+ return output.toString()
21
+ }
22
+
23
+ private fun appendTags(
24
+ output: StringBuilder,
25
+ tags: JSONArray
26
+ ) {
27
+ output.append('[')
28
+ for (tagIndex in 0 until tags.length()) {
29
+ if (tagIndex > 0) output.append(',')
30
+ val tag = tags.getJSONArray(tagIndex)
31
+ output.append('[')
32
+ for (valueIndex in 0 until tag.length()) {
33
+ if (valueIndex > 0) output.append(',')
34
+ appendString(
35
+ output,
36
+ tag.getString(valueIndex)
37
+ )
38
+ }
39
+ output.append(']')
40
+ }
41
+ output.append(']')
42
+ }
43
+
44
+ private fun appendString(
45
+ output: StringBuilder,
46
+ value: String
47
+ ) {
48
+ output.append('"')
49
+ value.forEach { character ->
50
+ when (character) {
51
+ '"' -> output.append("\\\"")
52
+ '\\' -> output.append("\\\\")
53
+ '\b' -> output.append("\\b")
54
+ ' ' -> output.append("\\f")
55
+ '\n' -> output.append("\\n")
56
+ '\r' -> output.append("\\r")
57
+ '\t' -> output.append("\\t")
58
+ else -> {
59
+ if (character.code < 0x20) {
60
+ output.append("\\u")
61
+ output.append(
62
+ character.code
63
+ .toString(16)
64
+ .padStart(4, '0')
65
+ )
66
+ } else {
67
+ output.append(character)
68
+ }
69
+ }
70
+ }
71
+ }
72
+ output.append('"')
73
+ }
74
+ }
@@ -60,4 +60,104 @@ object Secp256k1 {
60
60
  .field
61
61
  .characteristic
62
62
 
63
+ internal fun privateScalar(
64
+ privateKey: ByteArray
65
+ ): BigInteger {
66
+ require(privateKey.size == 32) {
67
+ "Private key must be 32 bytes"
68
+ }
69
+
70
+ val scalar = BigInteger(1, privateKey)
71
+ require(
72
+ scalar.signum() > 0 && scalar < N
73
+ ) {
74
+ "Invalid private key"
75
+ }
76
+
77
+ return scalar
78
+ }
79
+
80
+ internal fun publicPoint(
81
+ privateKey: ByteArray
82
+ ): ECPoint {
83
+ return G
84
+ .multiply(privateScalar(privateKey))
85
+ .normalize()
86
+ }
87
+
88
+ internal fun xOnlyPublicKey(
89
+ privateKey: ByteArray
90
+ ): ByteArray {
91
+ return fixedSizeBytes(
92
+ publicPoint(privateKey)
93
+ .affineXCoord
94
+ .toBigInteger()
95
+ )
96
+ }
97
+
98
+ internal fun liftX(
99
+ xBytes: ByteArray
100
+ ): ECPoint {
101
+ require(xBytes.size == 32) {
102
+ "Public key must be 32 bytes"
103
+ }
104
+
105
+ val x = BigInteger(1, xBytes)
106
+ require(x < P) {
107
+ "Invalid x coordinate"
108
+ }
109
+
110
+ val curve = CURVE.curve
111
+ val alpha =
112
+ x.modPow(BigInteger.valueOf(3), P)
113
+ .add(curve.b.toBigInteger())
114
+ .mod(P)
115
+ var y =
116
+ alpha.modPow(
117
+ P.add(BigInteger.ONE)
118
+ .divide(BigInteger.valueOf(4)),
119
+ P
120
+ )
121
+
122
+ require(y.multiply(y).mod(P) == alpha) {
123
+ "Invalid public key"
124
+ }
125
+
126
+ if (y.testBit(0)) {
127
+ y = P.subtract(y)
128
+ }
129
+
130
+ val point = curve.createPoint(x, y)
131
+ require(!point.isInfinity && point.isValid) {
132
+ "Invalid public key"
133
+ }
134
+
135
+ return point
136
+ }
137
+
138
+ internal fun fixedSizeBytes(
139
+ value: BigInteger
140
+ ): ByteArray {
141
+ require(value.signum() >= 0) {
142
+ "Value must not be negative"
143
+ }
144
+
145
+ val encoded = value.toByteArray()
146
+ val offset =
147
+ if (encoded.size > 32 && encoded[0] == 0.toByte()) 1
148
+ else 0
149
+ val length = encoded.size - offset
150
+
151
+ require(length <= 32) {
152
+ "Value does not fit in 32 bytes"
153
+ }
154
+
155
+ return ByteArray(32).also { output ->
156
+ encoded.copyInto(
157
+ output,
158
+ destinationOffset = 32 - length,
159
+ startIndex = offset
160
+ )
161
+ }
162
+ }
63
163
  }
@@ -1,42 +1,67 @@
1
1
  package com.pakstr.app.signer
2
2
 
3
3
  import android.content.Context
4
+ import com.pakstr.app.signer.bunker.Nip46Client
5
+ import com.pakstr.app.signer.bunker.UnavailableNip46Client
4
6
  import com.pakstr.app.signer.interfaces.NostrSigner
5
7
 
6
- class BunkerSigner(context: Context) : NostrSigner {
8
+ class BunkerSigner(
9
+ @Suppress("UNUSED_PARAMETER") context: Context,
10
+ private val client: Nip46Client = UnavailableNip46Client
11
+ ) : NostrSigner {
7
12
  override suspend fun getPublicKey(): String {
8
- TODO("Not yet implemented")
13
+ return client.getPublicKey()
9
14
  }
10
15
 
11
16
  override suspend fun signEvent(eventJson: String): String {
12
- TODO("Not yet implemented")
17
+ return client.signEvent(eventJson)
13
18
  }
14
19
 
15
20
  override suspend fun nip04Encrypt(
16
21
  pubkey: String,
17
22
  plaintext: String
18
23
  ): String {
19
- TODO("Not yet implemented")
24
+ return client.nip04Encrypt(
25
+ pubkey,
26
+ plaintext
27
+ )
20
28
  }
21
29
 
22
30
  override suspend fun nip04Decrypt(
23
31
  pubkey: String,
24
32
  ciphertext: String
25
33
  ): String {
26
- TODO("Not yet implemented")
34
+ return client.nip04Decrypt(
35
+ pubkey,
36
+ ciphertext
37
+ )
27
38
  }
28
39
 
29
40
  override suspend fun nip44Encrypt(
30
41
  pubkey: String,
31
42
  plaintext: String
32
43
  ): String {
33
- TODO("Not yet implemented")
44
+ return client.nip44Encrypt(
45
+ pubkey,
46
+ plaintext
47
+ )
34
48
  }
35
49
 
36
50
  override suspend fun nip44Decrypt(
37
51
  pubkey: String,
38
52
  ciphertext: String
39
53
  ): String {
40
- TODO("Not yet implemented")
54
+ return client.nip44Decrypt(
55
+ pubkey,
56
+ ciphertext
57
+ )
58
+ }
59
+
60
+ fun disconnect() {
61
+ client.disconnect()
62
+ }
63
+
64
+ fun close() {
65
+ client.close()
41
66
  }
42
67
  }
@@ -337,6 +337,11 @@ class NostrBridge(
337
337
  result
338
338
  )
339
339
 
340
+ } catch (e: SignerUnavailableException) {
341
+ resolve(
342
+ callbackId,
343
+ signerErrorResponse(e)
344
+ )
340
345
  } catch (e: Exception) {
341
346
  AppDebugLogger.error(
342
347
 
@@ -351,10 +356,7 @@ class NostrBridge(
351
356
  )
352
357
  resolve(
353
358
  callbackId,
354
- errorResponse(
355
- "SIGNER_ERROR",
356
- e.message ?: ""
357
- )
359
+ signerErrorResponse(e)
358
360
  )
359
361
  }
360
362
  }
@@ -390,6 +392,11 @@ class NostrBridge(
390
392
  result
391
393
  )
392
394
 
395
+ } catch (e: SignerUnavailableException) {
396
+ resolve(
397
+ callbackId,
398
+ signerErrorResponse(e)
399
+ )
393
400
  } catch (e: Exception) {
394
401
  AppDebugLogger.error(
395
402
 
@@ -404,10 +411,7 @@ class NostrBridge(
404
411
  )
405
412
  resolve(
406
413
  callbackId,
407
- errorResponse(
408
- "SIGNER_ERROR",
409
- e.message ?: ""
410
- )
414
+ signerErrorResponse(e)
411
415
  )
412
416
  }
413
417
  }
@@ -503,4 +507,27 @@ class NostrBridge(
503
507
  }
504
508
  .toString()
505
509
  }
506
- }
510
+
511
+ companion object {
512
+ internal fun signerErrorResponse(error: Exception): String {
513
+ return JSONObject()
514
+ .put(
515
+ "error",
516
+ if (error is SignerUnavailableException) {
517
+ "SIGNER_UNAVAILABLE"
518
+ } else {
519
+ "SIGNER_ERROR"
520
+ }
521
+ )
522
+ .put(
523
+ "message",
524
+ error.message ?: if (error is SignerUnavailableException) {
525
+ "Signer unavailable"
526
+ } else {
527
+ "Unknown signer error"
528
+ }
529
+ )
530
+ .toString()
531
+ }
532
+ }
533
+ }