pakstr 0.20.0 → 0.21.0
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 +136 -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 +11 -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
package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingClientTest.kt
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
package com.pakstr.app.signer.bunker
|
|
2
|
+
|
|
3
|
+
import com.pakstr.app.crypto.Bip340Hash
|
|
4
|
+
import com.pakstr.app.crypto.Nip46ClientKey
|
|
5
|
+
import com.pakstr.app.signer.NostrEventBuilder
|
|
6
|
+
import kotlinx.coroutines.CoroutineStart
|
|
7
|
+
import kotlinx.coroutines.async
|
|
8
|
+
import kotlinx.coroutines.runBlocking
|
|
9
|
+
import org.json.JSONArray
|
|
10
|
+
import org.json.JSONObject
|
|
11
|
+
import org.junit.After
|
|
12
|
+
import org.junit.Assert.assertEquals
|
|
13
|
+
import org.junit.Assert.assertNull
|
|
14
|
+
import org.junit.Assert.assertTrue
|
|
15
|
+
import org.junit.Before
|
|
16
|
+
import org.junit.Test
|
|
17
|
+
|
|
18
|
+
class Nip46PairingClientTest {
|
|
19
|
+
private lateinit var clientKey: Nip46ClientKey
|
|
20
|
+
private lateinit var signerKey: Nip46ClientKey
|
|
21
|
+
private lateinit var factory: FakeConnectionFactory
|
|
22
|
+
private lateinit var client: Nip46PairingClient
|
|
23
|
+
|
|
24
|
+
@Before
|
|
25
|
+
fun setup() {
|
|
26
|
+
clientKey = Nip46ClientKey.fromPrivateKey(privateKey(1))
|
|
27
|
+
signerKey = Nip46ClientKey.fromPrivateKey(privateKey(2))
|
|
28
|
+
factory = FakeConnectionFactory()
|
|
29
|
+
client =
|
|
30
|
+
Nip46PairingClient(
|
|
31
|
+
relayUrls = listOf(RELAY_URL),
|
|
32
|
+
clientKey = clientKey,
|
|
33
|
+
pairingSecret = PAIRING_SECRET,
|
|
34
|
+
connectionFactory = factory
|
|
35
|
+
)
|
|
36
|
+
factory.connection.open()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@After
|
|
40
|
+
fun teardown() {
|
|
41
|
+
client.close()
|
|
42
|
+
clientKey.close()
|
|
43
|
+
signerKey.close()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Test
|
|
47
|
+
fun pairing_uri_is_not_exposed_before_subscription_readiness() = runBlocking {
|
|
48
|
+
recreateClientWithoutOpeningRelay()
|
|
49
|
+
val exposedUri = async(start = CoroutineStart.UNDISPATCHED) {
|
|
50
|
+
check(client.awaitRelayReady(1_000))
|
|
51
|
+
PAIRING_URI
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
assertTrue(!exposedUri.isCompleted)
|
|
55
|
+
|
|
56
|
+
factory.connection.open()
|
|
57
|
+
|
|
58
|
+
assertEquals(PAIRING_URI, exposedUri.await())
|
|
59
|
+
val messages = factory.connection.messages
|
|
60
|
+
assertEquals(1, messages.size)
|
|
61
|
+
assertEquals("REQ", JSONArray(messages.single()).getString(0))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Test
|
|
65
|
+
fun response_after_readiness_completes_pairing() = runBlocking {
|
|
66
|
+
assertTrue(client.awaitRelayReady(100))
|
|
67
|
+
|
|
68
|
+
factory.connection.receiveEvent(responseEvent(PAIRING_SECRET))
|
|
69
|
+
|
|
70
|
+
assertEquals(
|
|
71
|
+
signerKey.publicKeyHex,
|
|
72
|
+
requireNotNull(client.awaitConnection(100)).remoteSignerPubkey
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Test
|
|
77
|
+
fun response_cannot_precede_readiness_when_uri_exposure_is_blocked() = runBlocking {
|
|
78
|
+
recreateClientWithoutOpeningRelay()
|
|
79
|
+
val exposedUri = async(start = CoroutineStart.UNDISPATCHED) {
|
|
80
|
+
check(client.awaitRelayReady(1_000))
|
|
81
|
+
PAIRING_URI
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
assertTrue(!exposedUri.isCompleted)
|
|
85
|
+
factory.connection.open()
|
|
86
|
+
assertEquals(PAIRING_URI, exposedUri.await())
|
|
87
|
+
factory.connection.receiveEvent(responseEvent(PAIRING_SECRET))
|
|
88
|
+
|
|
89
|
+
assertEquals(
|
|
90
|
+
signerKey.publicKeyHex,
|
|
91
|
+
requireNotNull(client.awaitConnection(100)).remoteSignerPubkey
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
fun relay_failure_does_not_leave_readiness_waiting_indefinitely() = runBlocking {
|
|
97
|
+
recreateClientWithoutOpeningRelay()
|
|
98
|
+
factory.connection.fail()
|
|
99
|
+
|
|
100
|
+
assertTrue(!client.awaitRelayReady(25))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Test
|
|
104
|
+
fun matching_secret_completes_pairing() = runBlocking {
|
|
105
|
+
factory.connection.receiveEvent(
|
|
106
|
+
responseEvent(PAIRING_SECRET)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
val config = requireNotNull(client.awaitConnection(100))
|
|
110
|
+
assertEquals(signerKey.publicKeyHex, config.remoteSignerPubkey)
|
|
111
|
+
assertEquals(listOf(RELAY_URL), config.relayUrls)
|
|
112
|
+
assertNull(config.pairingSecret)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Test
|
|
116
|
+
fun amber_connect_response_with_short_id_completes_pairing() = runBlocking {
|
|
117
|
+
factory.connection.receiveEvent(
|
|
118
|
+
responseEvent(
|
|
119
|
+
result = PAIRING_SECRET,
|
|
120
|
+
responseId = AMBER_RESPONSE_ID
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
val config = requireNotNull(client.awaitConnection(100))
|
|
125
|
+
assertEquals(signerKey.publicKeyHex, config.remoteSignerPubkey)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@Test
|
|
129
|
+
fun ack_wrong_secret_and_invalid_event_are_ignored() = runBlocking {
|
|
130
|
+
factory.connection.receiveEvent(responseEvent("ack"))
|
|
131
|
+
factory.connection.receiveEvent(responseEvent("wrong-secret"))
|
|
132
|
+
factory.connection.receiveEvent(
|
|
133
|
+
JSONObject(responseEvent(PAIRING_SECRET))
|
|
134
|
+
.put("sig", "0".repeat(128))
|
|
135
|
+
.toString()
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
assertNull(client.awaitConnection(25))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Test
|
|
142
|
+
fun close_is_idempotent_and_unblocks_waiter() = runBlocking {
|
|
143
|
+
client.close()
|
|
144
|
+
client.close()
|
|
145
|
+
|
|
146
|
+
assertTrue(factory.connection.closed)
|
|
147
|
+
assertTrue(factory.closed)
|
|
148
|
+
assertNull(client.awaitConnection(25))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun recreateClientWithoutOpeningRelay() {
|
|
152
|
+
client.close()
|
|
153
|
+
factory = FakeConnectionFactory()
|
|
154
|
+
client =
|
|
155
|
+
Nip46PairingClient(
|
|
156
|
+
relayUrls = listOf(RELAY_URL),
|
|
157
|
+
clientKey = clientKey,
|
|
158
|
+
pairingSecret = PAIRING_SECRET,
|
|
159
|
+
connectionFactory = factory
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private fun responseEvent(
|
|
164
|
+
result: String,
|
|
165
|
+
responseId: String = REQUEST_ID
|
|
166
|
+
): String {
|
|
167
|
+
val response =
|
|
168
|
+
JSONObject()
|
|
169
|
+
.put("id", responseId)
|
|
170
|
+
.put("result", result)
|
|
171
|
+
val encrypted =
|
|
172
|
+
signerKey.encryptNip44(
|
|
173
|
+
response.toString(),
|
|
174
|
+
clientKey.publicKeyHex
|
|
175
|
+
)
|
|
176
|
+
val event =
|
|
177
|
+
JSONObject()
|
|
178
|
+
.put("pubkey", signerKey.publicKeyHex)
|
|
179
|
+
.put("created_at", CREATED_AT)
|
|
180
|
+
.put("kind", 24133)
|
|
181
|
+
.put(
|
|
182
|
+
"tags",
|
|
183
|
+
JSONArray().put(
|
|
184
|
+
JSONArray()
|
|
185
|
+
.put("p")
|
|
186
|
+
.put(clientKey.publicKeyHex)
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
.put("content", encrypted)
|
|
190
|
+
NostrEventBuilder.addId(event)
|
|
191
|
+
val eventId = Bip340Hash.hexToBytes(event.getString("id"))
|
|
192
|
+
val signature = signerKey.sign(eventId)
|
|
193
|
+
event.put("sig", Bip340Hash.bytesToHex(signature))
|
|
194
|
+
eventId.fill(0)
|
|
195
|
+
signature.fill(0)
|
|
196
|
+
return event.toString()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private fun privateKey(value: Int): ByteArray {
|
|
200
|
+
return ByteArray(32).also { it[31] = value.toByte() }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private class FakeConnectionFactory : Nip46RelayConnectionFactory {
|
|
204
|
+
lateinit var connection: FakeConnection
|
|
205
|
+
private set
|
|
206
|
+
var closed = false
|
|
207
|
+
private set
|
|
208
|
+
|
|
209
|
+
override fun connect(
|
|
210
|
+
relayUrl: String,
|
|
211
|
+
listener: Nip46RelayConnectionListener
|
|
212
|
+
): Nip46RelayConnection {
|
|
213
|
+
return FakeConnection(listener).also {
|
|
214
|
+
connection = it
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
override fun close() {
|
|
219
|
+
closed = true
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private class FakeConnection(
|
|
224
|
+
private val listener: Nip46RelayConnectionListener
|
|
225
|
+
) : Nip46RelayConnection {
|
|
226
|
+
private lateinit var subscriptionId: String
|
|
227
|
+
val messages = mutableListOf<String>()
|
|
228
|
+
var closed = false
|
|
229
|
+
private set
|
|
230
|
+
|
|
231
|
+
override fun send(message: String): Boolean {
|
|
232
|
+
messages += message
|
|
233
|
+
val frame = JSONArray(message)
|
|
234
|
+
if (frame.getString(0) == "REQ") {
|
|
235
|
+
subscriptionId = frame.getString(1)
|
|
236
|
+
}
|
|
237
|
+
return !closed
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
override fun close() {
|
|
241
|
+
closed = true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fun open() {
|
|
245
|
+
listener.onOpen()
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fun fail() {
|
|
249
|
+
listener.onFailure()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fun receiveEvent(eventJson: String) {
|
|
253
|
+
listener.onMessage(
|
|
254
|
+
JSONArray()
|
|
255
|
+
.put("EVENT")
|
|
256
|
+
.put(subscriptionId)
|
|
257
|
+
.put(JSONObject(eventJson))
|
|
258
|
+
.toString()
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private companion object {
|
|
264
|
+
const val RELAY_URL = "wss://relay.example"
|
|
265
|
+
const val PAIRING_SECRET = "pairing-secret-value"
|
|
266
|
+
const val PAIRING_URI = "nostrconnect://not-exposed-until-ready"
|
|
267
|
+
const val CREATED_AT = 1_700_000_000L
|
|
268
|
+
const val AMBER_RESPONSE_ID = "a1b2c3"
|
|
269
|
+
val REQUEST_ID = "a".repeat(64)
|
|
270
|
+
}
|
|
271
|
+
}
|
package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingQrCodeTest.kt
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package com.pakstr.app.signer.bunker
|
|
2
|
+
|
|
3
|
+
import com.google.zxing.BinaryBitmap
|
|
4
|
+
import com.google.zxing.RGBLuminanceSource
|
|
5
|
+
import com.google.zxing.common.HybridBinarizer
|
|
6
|
+
import com.google.zxing.qrcode.QRCodeReader
|
|
7
|
+
import org.junit.Assert.assertEquals
|
|
8
|
+
import org.junit.Assert.assertThrows
|
|
9
|
+
import org.junit.Test
|
|
10
|
+
|
|
11
|
+
class Nip46PairingQrCodeTest {
|
|
12
|
+
|
|
13
|
+
@Test
|
|
14
|
+
fun qr_code_decodes_to_exact_nostrconnect_uri() {
|
|
15
|
+
val uri =
|
|
16
|
+
"nostrconnect://${"a".repeat(64)}" +
|
|
17
|
+
"?relay=wss%3A%2F%2Frelay.example" +
|
|
18
|
+
"&perms=sign_event%2Cnip44_encrypt" +
|
|
19
|
+
"&name=Pakstr" +
|
|
20
|
+
"&secret=exact-pairing-secret"
|
|
21
|
+
val matrix = Nip46PairingQrCode.encode(uri, 512)
|
|
22
|
+
val pixels = IntArray(matrix.width * matrix.height) { index ->
|
|
23
|
+
val x = index % matrix.width
|
|
24
|
+
val y = index / matrix.width
|
|
25
|
+
if (matrix[x, y]) BLACK else WHITE
|
|
26
|
+
}
|
|
27
|
+
val bitmap = BinaryBitmap(
|
|
28
|
+
HybridBinarizer(
|
|
29
|
+
RGBLuminanceSource(
|
|
30
|
+
matrix.width,
|
|
31
|
+
matrix.height,
|
|
32
|
+
pixels
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
assertEquals(uri, QRCodeReader().decode(bitmap).text)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Test
|
|
41
|
+
fun rejects_blank_payload_and_invalid_size() {
|
|
42
|
+
assertThrows(IllegalArgumentException::class.java) {
|
|
43
|
+
Nip46PairingQrCode.encode("", 512)
|
|
44
|
+
}
|
|
45
|
+
assertThrows(IllegalArgumentException::class.java) {
|
|
46
|
+
Nip46PairingQrCode.encode("nostrconnect://pairing", 0)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private companion object {
|
|
51
|
+
const val BLACK = -0x1000000
|
|
52
|
+
const val WHITE = -0x1
|
|
53
|
+
}
|
|
54
|
+
}
|
package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingUriTest.kt
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
package com.pakstr.app.signer.bunker
|
|
2
|
+
|
|
3
|
+
import com.pakstr.app.crypto.Nip46ClientKey
|
|
4
|
+
import org.junit.Assert.assertEquals
|
|
5
|
+
import org.junit.Assert.assertFalse
|
|
6
|
+
import org.junit.Assert.assertTrue
|
|
7
|
+
import org.junit.Test
|
|
8
|
+
import java.net.URI
|
|
9
|
+
import java.net.URLDecoder
|
|
10
|
+
|
|
11
|
+
class Nip46PairingUriTest {
|
|
12
|
+
@Test
|
|
13
|
+
fun generated_uri_is_canonical_and_contains_required_fields() {
|
|
14
|
+
val clientKey = Nip46ClientKey.fromPrivateKey(privateKey(1))
|
|
15
|
+
try {
|
|
16
|
+
val uri =
|
|
17
|
+
Nip46PairingUri.create(
|
|
18
|
+
clientPubkey = clientKey.publicKeyHex,
|
|
19
|
+
relayUrls = listOf(
|
|
20
|
+
"wss://relay-one.example/path",
|
|
21
|
+
"wss://relay-two.example"
|
|
22
|
+
),
|
|
23
|
+
secret = "one-time-secret",
|
|
24
|
+
permissions = "sign_event,nip44_encrypt",
|
|
25
|
+
clientName = "Pakstr"
|
|
26
|
+
).encode()
|
|
27
|
+
val parsed = URI(uri)
|
|
28
|
+
val parameters =
|
|
29
|
+
parsed.rawQuery.split("&").map { parameter ->
|
|
30
|
+
val parts = parameter.split("=", limit = 2)
|
|
31
|
+
URLDecoder.decode(parts[0], "UTF-8") to
|
|
32
|
+
URLDecoder.decode(parts[1], "UTF-8")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
assertEquals("nostrconnect", parsed.scheme)
|
|
36
|
+
assertEquals(clientKey.publicKeyHex, parsed.host)
|
|
37
|
+
assertEquals(
|
|
38
|
+
listOf(
|
|
39
|
+
"wss://relay-one.example/path",
|
|
40
|
+
"wss://relay-two.example"
|
|
41
|
+
),
|
|
42
|
+
parameters.filter { it.first == "relay" }.map { it.second }
|
|
43
|
+
)
|
|
44
|
+
assertEquals(
|
|
45
|
+
"one-time-secret",
|
|
46
|
+
parameters.single { it.first == "secret" }.second
|
|
47
|
+
)
|
|
48
|
+
assertTrue(uri.contains("name=Pakstr"))
|
|
49
|
+
assertFalse(
|
|
50
|
+
Nip46PairingUri.create(
|
|
51
|
+
clientKey.publicKeyHex,
|
|
52
|
+
listOf("wss://relay.example"),
|
|
53
|
+
"secret"
|
|
54
|
+
).toString().contains("secret")
|
|
55
|
+
)
|
|
56
|
+
} finally {
|
|
57
|
+
clientKey.close()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Test
|
|
62
|
+
fun generated_uri_rejects_insecure_relays() {
|
|
63
|
+
val clientKey = Nip46ClientKey.fromPrivateKey(privateKey(1))
|
|
64
|
+
try {
|
|
65
|
+
org.junit.Assert.assertThrows(IllegalArgumentException::class.java) {
|
|
66
|
+
Nip46PairingUri.create(
|
|
67
|
+
clientKey.publicKeyHex,
|
|
68
|
+
listOf("ws://relay.example"),
|
|
69
|
+
"secret"
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
clientKey.close()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private fun privateKey(value: Int): ByteArray {
|
|
78
|
+
return ByteArray(32).also { it[31] = value.toByte() }
|
|
79
|
+
}
|
|
80
|
+
}
|