pakstr 0.0.4 → 0.2.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.
Files changed (32) hide show
  1. package/android-template/app/build.gradle.kts +3 -0
  2. package/android-template/app/src/androidTest/java/com/pakstr/app/ExampleInstrumentedTest.kt +8 -3
  3. package/android-template/app/src/androidTest/java/com/pakstr/app/signer/NostrEventBuilderTest.kt +67 -0
  4. package/android-template/app/src/androidTest/java/com/pakstr/app/signer/NostrSignatureVerifierTest.kt +43 -0
  5. package/android-template/app/src/main/AndroidManifest.xml +15 -3
  6. package/android-template/app/src/main/assets/nip07.js +85 -4
  7. package/android-template/app/src/main/assets/www/index.html +215 -0
  8. package/android-template/app/src/main/java/com/pakstr/app/ApplicationClass.kt +2 -0
  9. package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +35 -10
  10. package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +121 -101
  11. package/android-template/app/src/main/java/com/pakstr/app/ShellRuntime.kt +5 -6
  12. package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +69 -20
  13. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Verifier.kt +318 -0
  14. package/android-template/app/src/main/java/com/pakstr/app/crypto/CryptoInitializer.kt +34 -0
  15. package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip04Crypto.kt +33 -0
  16. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEncryption.kt +16 -0
  17. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventHasher.kt +100 -0
  18. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventVerifier.kt +54 -0
  19. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrSignatureVerifier.kt +43 -0
  20. package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +63 -0
  21. package/android-template/app/src/main/java/com/pakstr/app/debug/AppDebugLogger.kt +21 -0
  22. package/android-template/app/src/main/java/com/pakstr/app/signer/AmberSigner.kt +427 -74
  23. package/android-template/app/src/main/java/com/pakstr/app/signer/BunkerSigner.kt +14 -0
  24. package/android-template/app/src/main/java/com/pakstr/app/signer/FakeSigner.kt +14 -0
  25. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +297 -32
  26. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrEventBuilder.kt +39 -0
  27. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +103 -73
  28. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerType.kt +0 -1
  29. package/android-template/app/src/main/java/com/pakstr/app/signer/exceptions/SignerUnavailableException.kt +5 -0
  30. package/android-template/app/src/main/java/com/pakstr/app/signer/interfaces/NostrSigner.kt +10 -0
  31. package/android-template/app/src/main/res/values/strings.xml +1 -1
  32. package/package.json +1 -1
@@ -32,6 +32,7 @@ android {
32
32
  }
33
33
  buildFeatures {
34
34
  compose = true
35
+ buildConfig = true
35
36
  }
36
37
  }
37
38
 
@@ -56,4 +57,6 @@ dependencies {
56
57
 
57
58
  implementation(libs.material)
58
59
  implementation(libs.androidx.core.splashscreen)
60
+
61
+ implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
59
62
  }
@@ -17,8 +17,13 @@ import org.junit.Assert.*
17
17
  class ExampleInstrumentedTest {
18
18
  @Test
19
19
  fun useAppContext() {
20
- // Context of the app under test.
21
- val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22
- assertEquals("com.makakrug.nostrappshell", appContext.packageName)
20
+ val appContext =
21
+ InstrumentationRegistry.getInstrumentation()
22
+ .targetContext
23
+
24
+ assertEquals(
25
+ "com.pakstr.app",
26
+ appContext.packageName
27
+ )
23
28
  }
24
29
  }
@@ -0,0 +1,67 @@
1
+ package com.pakstr.app.signer
2
+
3
+ import org.json.JSONArray
4
+ import org.json.JSONObject
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Test
7
+
8
+
9
+ class NostrEventBuilderTest {
10
+
11
+
12
+ @Test
13
+ fun event_id_is_generated() {
14
+
15
+
16
+ val event =
17
+ JSONObject()
18
+ .apply {
19
+
20
+ put(
21
+ "pubkey",
22
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
23
+ )
24
+
25
+ put(
26
+ "created_at",
27
+ 1234567890
28
+ )
29
+
30
+ put(
31
+ "kind",
32
+ 1
33
+ )
34
+
35
+ put(
36
+ "tags",
37
+ JSONArray()
38
+ )
39
+
40
+ put(
41
+ "content",
42
+ "hello"
43
+ )
44
+
45
+ }
46
+
47
+
48
+ val result =
49
+ NostrEventBuilder
50
+ .addId(event)
51
+
52
+
53
+
54
+ val id =
55
+ result.getString(
56
+ "id"
57
+ )
58
+
59
+
60
+ assertEquals(
61
+ 64,
62
+ id.length
63
+ )
64
+
65
+ }
66
+
67
+ }
@@ -0,0 +1,43 @@
1
+ package com.pakstr.app.signer
2
+
3
+ import com.pakstr.app.crypto.NostrSignatureVerifier
4
+ import org.junit.Assert.assertTrue
5
+ import org.junit.Test
6
+
7
+
8
+ class NostrSignatureVerifierTest {
9
+
10
+
11
+ @Test
12
+ fun signature_verification_returns_true() {
13
+
14
+
15
+ val valid =
16
+ NostrSignatureVerifier.verify(
17
+ eventId = TEST_ID,
18
+ pubkey = TEST_PUBKEY,
19
+ signature = TEST_SIGNATURE
20
+ )
21
+
22
+
23
+ assertTrue(valid)
24
+
25
+ }
26
+
27
+
28
+ companion object {
29
+
30
+ private const val TEST_ID =
31
+ "1a3abb615ca8c2bd2f0fe5764df3cc734f3ff5774300e81bb1fad5d8bc948a21"
32
+
33
+
34
+ private const val TEST_PUBKEY =
35
+ "cb227f60cb2a6d982ecbfc51b9199d340afc00b1ef81b67d8e3778e097e70d2c"
36
+
37
+
38
+ private const val TEST_SIGNATURE =
39
+ "b64fdbd205c38056b8d366e409e7bf5796cad60789733b377c76c07cd74fe9394bbbfaada66ccd7fa8c3ad008139b9c224b01d7ed95e29d449b2d6b750be0e6f"
40
+
41
+ }
42
+
43
+ }
@@ -1,8 +1,20 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
3
 
4
- <uses-permission android:name="android.permission.INTERNET"/>
5
- <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
4
+ <uses-permission android:name="android.permission.INTERNET" />
5
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
6
+
7
+
8
+ <queries>
9
+ <package android:name="com.greenart7c3.nostrsigner" />
10
+ <intent>
11
+ <action android:name="com.greenart7c3.nostrsigner.GET_PUBLIC_KEY" />
12
+ </intent>
13
+ <intent>
14
+ <action android:name="com.greenart7c3.nostrsigner.SIGN_EVENT" />
15
+ </intent>
16
+
17
+ </queries>
6
18
 
7
19
  <application
8
20
  android:name=".ApplicationClass"
@@ -11,9 +23,9 @@
11
23
  android:fullBackupContent="@xml/backup_rules"
12
24
  android:icon="@mipmap/ic_launcher"
13
25
  android:label="@string/app_name"
26
+ android:networkSecurityConfig="@xml/network_security_config"
14
27
  android:roundIcon="@mipmap/ic_launcher_round"
15
28
  android:supportsRtl="true"
16
- android:networkSecurityConfig="@xml/network_security_config"
17
29
  android:usesCleartextTraffic="true">
18
30
 
19
31
  <activity
@@ -9,7 +9,10 @@
9
9
 
10
10
  const id = crypto.randomUUID();
11
11
 
12
- pending.set(id, { resolve, reject });
12
+ pending.set(id, {
13
+ resolve,
14
+ reject
15
+ });
13
16
 
14
17
  window.AndroidBridge.call(
15
18
  method,
@@ -20,16 +23,94 @@
20
23
  }
21
24
 
22
25
  window.__nostrResolve = (id, result) => {
26
+
23
27
  const p = pending.get(id);
24
28
  if (!p) return;
25
29
 
26
30
  pending.delete(id);
31
+
32
+ try {
33
+ const parsed = JSON.parse(result);
34
+
35
+ if (parsed.error) {
36
+ p.reject(parsed);
37
+ return;
38
+ }
39
+
40
+ } catch (_) {
41
+ // Normal string response
42
+ }
43
+
27
44
  p.resolve(result);
28
45
  };
29
46
 
30
47
  window.nostr = {
31
- getPublicKey: () => callNative("getPublicKey"),
32
- signEvent: (e) => callNative("signEvent", e)
48
+
49
+ getPublicKey: async () => {
50
+ return await callNative("getPublicKey");
51
+ },
52
+
53
+ signEvent: async (event) => {
54
+
55
+ const result = await callNative(
56
+ "signEvent",
57
+ event
58
+ );
59
+
60
+ return JSON.parse(result);
61
+ },
62
+
63
+ nip04: {
64
+
65
+ encrypt: async (pubkey, plaintext) => {
66
+
67
+ return await callNative(
68
+ "nip04_encrypt",
69
+ {
70
+ pubkey,
71
+ plaintext
72
+ }
73
+ );
74
+ },
75
+
76
+ decrypt: async (pubkey, ciphertext) => {
77
+
78
+ return await callNative(
79
+ "nip04_decrypt",
80
+ {
81
+ pubkey,
82
+ ciphertext
83
+ }
84
+ );
85
+ }
86
+
87
+ },
88
+
89
+ nip44: {
90
+
91
+ encrypt: async (pubkey, plaintext) => {
92
+
93
+ return await callNative(
94
+ "nip44_encrypt",
95
+ {
96
+ pubkey,
97
+ plaintext
98
+ }
99
+ );
100
+ },
101
+
102
+ decrypt: async (pubkey, ciphertext) => {
103
+
104
+ return await callNative(
105
+ "nip44_decrypt",
106
+ {
107
+ pubkey,
108
+ ciphertext
109
+ }
110
+ );
111
+ }
112
+
113
+ }
33
114
  };
34
115
 
35
- })();
116
+ })();
@@ -0,0 +1,215 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Pakstr NIP-07 Test</title>
6
+
7
+ <style>
8
+ body {
9
+ font-family: sans-serif;
10
+ padding: 20px;
11
+ }
12
+
13
+ button {
14
+ padding: 12px;
15
+ margin: 8px;
16
+ font-size: 16px;
17
+ }
18
+
19
+ pre {
20
+ background: #eee;
21
+ padding: 10px;
22
+ white-space: pre-wrap;
23
+ word-break: break-word;
24
+ }
25
+ </style>
26
+ </head>
27
+
28
+ <body>
29
+
30
+ <h2>🚀 Pakstr NIP-07 Test</h2>
31
+
32
+
33
+ <button onclick="getPublicKey()">
34
+ Get Public Key
35
+ </button>
36
+
37
+
38
+ <button onclick="signEvent()">
39
+ Sign Event
40
+ </button>
41
+
42
+ <pre id="output">
43
+ Waiting...
44
+ </pre>
45
+
46
+
47
+ <script>
48
+
49
+
50
+ function log(data) {
51
+
52
+ document.getElementById("output").innerText =
53
+ typeof data === "string"
54
+ ? data
55
+ : JSON.stringify(data, null, 2);
56
+
57
+ console.log(data);
58
+
59
+ }
60
+
61
+
62
+ // Check provider after Pakstr injection
63
+ setTimeout(() => {
64
+
65
+ console.log(
66
+ "Nostr provider:",
67
+ window.nostr
68
+ );
69
+
70
+ }, 2000);
71
+
72
+
73
+
74
+ async function getPublicKey() {
75
+
76
+ try {
77
+
78
+ log("Requesting public key...");
79
+
80
+
81
+ if (!window.nostr) {
82
+
83
+ throw new Error(
84
+ "Nostr provider not available"
85
+ );
86
+
87
+ }
88
+
89
+
90
+ const pubkey =
91
+ await window.nostr.getPublicKey();
92
+
93
+
94
+ log({
95
+
96
+ success: true,
97
+
98
+ pubkey,
99
+
100
+ length:
101
+ pubkey.length,
102
+
103
+ format:
104
+ pubkey.startsWith("npub")
105
+ ? "npub"
106
+ : "hex"
107
+
108
+ });
109
+
110
+
111
+ } catch (e) {
112
+
113
+ log({
114
+
115
+ error:
116
+ e.message
117
+
118
+ });
119
+
120
+ }
121
+
122
+ }
123
+
124
+
125
+
126
+
127
+ async function signEvent() {
128
+
129
+ try {
130
+
131
+ log("Signing event...");
132
+
133
+
134
+ if (!window.nostr) {
135
+
136
+ throw new Error(
137
+ "Nostr provider not available"
138
+ );
139
+
140
+ }
141
+
142
+
143
+ const pubkey =
144
+ await window.nostr.getPublicKey();
145
+
146
+
147
+ const event = {
148
+
149
+ pubkey,
150
+
151
+ kind: 1,
152
+
153
+ content:
154
+ "Hello from Pakstr NIP-07",
155
+
156
+ tags: [],
157
+
158
+ created_at:
159
+ Math.floor(
160
+ Date.now() / 1000
161
+ )
162
+
163
+ };
164
+
165
+
166
+ console.log(
167
+ "Unsigned event",
168
+ event
169
+ );
170
+
171
+
172
+ const signedEvent =
173
+ await window.nostr.signEvent(
174
+ event
175
+ );
176
+
177
+
178
+ console.log(
179
+ "Signed event",
180
+ signedEvent
181
+ );
182
+
183
+
184
+ log({
185
+
186
+ success: true,
187
+
188
+ signedEvent
189
+
190
+ });
191
+
192
+
193
+ } catch (e) {
194
+
195
+ console.error(e);
196
+
197
+
198
+ log({
199
+
200
+ error:
201
+ e.message,
202
+
203
+ stack:
204
+ e.stack
205
+
206
+ });
207
+
208
+ }
209
+ }
210
+
211
+
212
+ </script>
213
+
214
+ </body>
215
+ </html>
@@ -1,11 +1,13 @@
1
1
  package com.pakstr.app
2
2
 
3
3
  import android.app.Application
4
+ import com.pakstr.app.crypto.CryptoInitializer
4
5
  import com.pakstr.app.utils.AppPreferences
5
6
 
6
7
  class ApplicationClass : Application() {
7
8
  override fun onCreate() {
8
9
  super.onCreate()
9
10
  AppPreferences.init(this)
11
+ CryptoInitializer.init()
10
12
  }
11
13
  }
@@ -10,7 +10,9 @@ class LocalServer(private val context: Context, port: Int) : NanoHTTPD(port) {
10
10
 
11
11
  override fun serve(session: IHTTPSession?): Response {
12
12
  // Fallback to root if URI is null
13
- val uri = session?.uri ?: "/"
13
+ val uri =
14
+ (session?.uri ?: "/")
15
+ .replace("..", "")
14
16
 
15
17
  // Map the root URL to index.html, otherwise target the requested file in the assets folder
16
18
  val filePath = when {
@@ -38,14 +40,37 @@ class LocalServer(private val context: Context, port: Int) : NanoHTTPD(port) {
38
40
  /**
39
41
  * Automatically detects the correct MIME type based on the file extension.
40
42
  */
41
- private fun getMimeType(file: String): String {
42
- // Extract extension from file path (e.g., "style.css" -> "css")
43
- val extension = MimeTypeMap.getFileExtensionFromUrl(file)
44
- ?.lowercase(Locale.ROOT)
45
- ?: ""
46
-
47
- // Use Android's built-in map to resolve the exact MIME type (handles PNG, JPG, JSON, WOFF2, etc.) [1]
48
- return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
49
- ?: "application/octet-stream" // Standard fallback for unknown binary files
43
+ private fun getMimeType(
44
+ file: String
45
+ ): String {
46
+
47
+ val extension =
48
+ MimeTypeMap
49
+ .getFileExtensionFromUrl(file)
50
+ ?.lowercase(Locale.ROOT)
51
+ ?: ""
52
+
53
+ return when (extension) {
54
+ "js" ->
55
+ "application/javascript"
56
+
57
+ "json" ->
58
+ "application/json"
59
+
60
+ "css" ->
61
+ "text/css"
62
+
63
+ "html" ->
64
+ "text/html"
65
+
66
+ "svg" ->
67
+ "image/svg+xml"
68
+
69
+ else ->
70
+ MimeTypeMap
71
+ .getSingleton()
72
+ .getMimeTypeFromExtension(extension)
73
+ ?: "application/octet-stream"
74
+ }
50
75
  }
51
76
  }