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.
- package/README.md +1 -1
- package/android-template/app/build.gradle.kts +4 -0
- package/android-template/app/src/androidTest/java/com/pakstr/app/signer/SignerManagerTest.kt +273 -0
- package/android-template/app/src/main/assets/www/index.html +0 -6
- package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +20 -4
- package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +130 -12
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Hash.kt +56 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Signer.kt +104 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Verifier.kt +20 -170
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip44Crypto.kt +513 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip46ClientKey.kt +140 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventHasher.kt +3 -28
- package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventSerializer.kt +74 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +100 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/BunkerSigner.kt +32 -7
- package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +36 -9
- package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +240 -163
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/BunkerConfig.kt +175 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/BunkerConnectionStore.kt +144 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Client.kt +79 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Models.kt +319 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingClient.kt +159 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingQrCode.kt +35 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingUri.kt +104 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Protocol.kt +695 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46RelayTransport.kt +489 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/RelayNip46Client.kt +403 -0
- package/android-template/app/src/main/java/com/pakstr/app/utils/BunkerPairingDialog.kt +82 -0
- package/android-template/app/src/main/java/com/pakstr/app/utils/SignerPickerBottomSheet.kt +78 -17
- package/android-template/app/src/main/res/layout/activity_layout.xml +1 -0
- package/android-template/app/src/main/res/layout/dialog_bunker_pairing.xml +75 -0
- package/android-template/app/src/main/res/layout/dialog_picker.xml +60 -31
- package/android-template/app/src/main/res/values/strings.xml +27 -0
- package/android-template/app/src/test/java/com/pakstr/app/LocalServerQueryKeepAliveTest.kt +118 -0
- package/android-template/app/src/test/java/com/pakstr/app/crypto/Bip340SignerTest.kt +133 -0
- package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip44CryptoTest.kt +199 -0
- package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip46ClientKeyProtocolTest.kt +82 -0
- package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip46ClientKeyTest.kt +52 -0
- package/android-template/app/src/test/java/com/pakstr/app/crypto/NostrEventSerializerTest.kt +42 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/NostrBridgeErrorTest.kt +32 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/BunkerConfigTest.kt +120 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46ModelsTest.kt +111 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingClientTest.kt +271 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingQrCodeTest.kt +54 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingUriTest.kt +80 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46ProtocolTest.kt +578 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46RelayTransportTest.kt +508 -0
- package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/RelayNip46ClientTest.kt +678 -0
- package/android-template/gradle/libs.versions.toml +8 -0
- 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(
|
|
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
|
-
|
|
13
|
+
return client.getPublicKey()
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
override suspend fun signEvent(eventJson: String): String {
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|