pakstr 0.0.4 → 0.1.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 (27) 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 +47 -7
  7. package/android-template/app/src/main/assets/www/index.html +220 -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 +102 -103
  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 +65 -18
  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/NostrEventHasher.kt +100 -0
  16. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventVerifier.kt +54 -0
  17. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrSignatureVerifier.kt +43 -0
  18. package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +63 -0
  19. package/android-template/app/src/main/java/com/pakstr/app/debug/AppDebugLogger.kt +21 -0
  20. package/android-template/app/src/main/java/com/pakstr/app/signer/AmberSigner.kt +142 -100
  21. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +115 -39
  22. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrEventBuilder.kt +39 -0
  23. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +83 -75
  24. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerType.kt +0 -1
  25. package/android-template/app/src/main/java/com/pakstr/app/signer/exceptions/SignerUnavailableException.kt +5 -0
  26. package/android-template/app/src/main/res/values/strings.xml +1 -1
  27. 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
@@ -5,31 +5,71 @@
5
5
  const pending = new Map();
6
6
 
7
7
  function callNative(method, payload) {
8
+
8
9
  return new Promise((resolve, reject) => {
9
10
 
10
11
  const id = crypto.randomUUID();
11
12
 
12
- pending.set(id, { resolve, reject });
13
+ pending.set(
14
+ id,
15
+ {
16
+ resolve,
17
+ reject
18
+ }
19
+ );
13
20
 
14
21
  window.AndroidBridge.call(
15
22
  method,
16
23
  id,
17
- payload ? JSON.stringify(payload) : ""
24
+ payload
25
+ ? JSON.stringify(payload)
26
+ : ""
18
27
  );
19
28
  });
20
29
  }
21
30
 
22
31
  window.__nostrResolve = (id, result) => {
23
- const p = pending.get(id);
32
+
33
+ const p =
34
+ pending.get(id);
35
+
24
36
  if (!p) return;
25
37
 
26
38
  pending.delete(id);
39
+
40
+ try {
41
+
42
+ const parsed =
43
+ JSON.parse(result);
44
+
45
+ if (parsed.error) {
46
+ p.reject(parsed);
47
+ return;
48
+ }
49
+ } catch (_) {
50
+ // Normal string response
51
+ }
27
52
  p.resolve(result);
28
53
  };
29
54
 
30
55
  window.nostr = {
31
- getPublicKey: () => callNative("getPublicKey"),
32
- signEvent: (e) => callNative("signEvent", e)
33
- };
34
56
 
35
- })();
57
+ getPublicKey: async () => {
58
+
59
+ return await callNative(
60
+ "getPublicKey"
61
+ );
62
+ },
63
+
64
+ signEvent: async (event) => {
65
+
66
+ const result =
67
+ await callNative(
68
+ "signEvent",
69
+ event
70
+ );
71
+
72
+ return JSON.parse(result);
73
+ }
74
+ };
75
+ })();
@@ -0,0 +1,220 @@
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
+
43
+ <pre id="output">
44
+ Waiting...
45
+ </pre>
46
+
47
+
48
+ <script>
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
+ console.log(
63
+ "Nostr provider:",
64
+ window.nostr
65
+ );
66
+
67
+
68
+ async function getPublicKey() {
69
+
70
+ try {
71
+
72
+ log("Requesting public key...");
73
+
74
+
75
+ if (!window.nostr) {
76
+
77
+ throw new Error(
78
+ "Nostr provider not available"
79
+ );
80
+
81
+ }
82
+
83
+
84
+ const pubkey =
85
+ await window.nostr.getPublicKey();
86
+
87
+
88
+ log({
89
+
90
+ success: true,
91
+
92
+ pubkey,
93
+
94
+ length: pubkey.length,
95
+
96
+ format:
97
+ pubkey.startsWith("npub")
98
+ ? "npub"
99
+ : "hex"
100
+
101
+ });
102
+
103
+
104
+ } catch (e) {
105
+
106
+ log({
107
+
108
+ error: e.message
109
+
110
+ });
111
+
112
+ }
113
+
114
+ }
115
+
116
+
117
+
118
+ async function signEvent() {
119
+
120
+ try {
121
+
122
+ log("Signing event...");
123
+
124
+ if (!window.nostr) {
125
+
126
+ throw new Error(
127
+
128
+ "Nostr provider not available"
129
+
130
+ );
131
+
132
+ }
133
+
134
+ const pubkey =
135
+
136
+ await window.nostr.getPublicKey();
137
+
138
+ const event = {
139
+
140
+ pubkey: pubkey,
141
+
142
+ kind: 1,
143
+
144
+ content:
145
+
146
+ "Hello from Pakstr NIP-07",
147
+
148
+ tags: [],
149
+
150
+ created_at:
151
+
152
+ Math.floor(
153
+
154
+ Date.now() / 1000
155
+
156
+ )
157
+
158
+ };
159
+
160
+ console.log(
161
+
162
+ "Unsigned event",
163
+
164
+ event
165
+
166
+ );
167
+
168
+ log({
169
+
170
+ step: "sending event",
171
+
172
+ event
173
+
174
+ });
175
+
176
+ const signedEvent =
177
+
178
+ await window.nostr.signEvent(
179
+
180
+ event
181
+
182
+ );
183
+
184
+ console.log(
185
+
186
+ "Signed event",
187
+
188
+ signedEvent
189
+
190
+ );
191
+
192
+ log({
193
+
194
+ success: true,
195
+
196
+ signedEvent
197
+
198
+ });
199
+
200
+ } catch (e) {
201
+
202
+ console.error(e);
203
+
204
+ log({
205
+
206
+ error: e.message,
207
+
208
+ stack: e.stack
209
+
210
+ });
211
+
212
+ }
213
+
214
+ }
215
+
216
+
217
+ </script>
218
+
219
+ </body>
220
+ </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
  }