pakstr 0.0.3 → 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.
- package/android-template/app/build.gradle.kts +3 -0
- package/android-template/app/src/androidTest/java/com/pakstr/app/ExampleInstrumentedTest.kt +8 -3
- package/android-template/app/src/androidTest/java/com/pakstr/app/signer/NostrEventBuilderTest.kt +67 -0
- package/android-template/app/src/androidTest/java/com/pakstr/app/signer/NostrSignatureVerifierTest.kt +43 -0
- package/android-template/app/src/main/AndroidManifest.xml +15 -3
- package/android-template/app/src/main/assets/nip07.js +47 -7
- package/android-template/app/src/main/assets/www/index.html +220 -0
- package/android-template/app/src/main/java/com/pakstr/app/ApplicationClass.kt +2 -0
- package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +35 -10
- package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +102 -103
- package/android-template/app/src/main/java/com/pakstr/app/ShellRuntime.kt +5 -6
- package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +65 -18
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Verifier.kt +318 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/CryptoInitializer.kt +34 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventHasher.kt +100 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventVerifier.kt +54 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrSignatureVerifier.kt +43 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +63 -0
- package/android-template/app/src/main/java/com/pakstr/app/debug/AppDebugLogger.kt +21 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/AmberSigner.kt +142 -100
- package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +115 -39
- package/android-template/app/src/main/java/com/pakstr/app/signer/NostrEventBuilder.kt +39 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +83 -75
- package/android-template/app/src/main/java/com/pakstr/app/signer/SignerType.kt +0 -1
- package/android-template/app/src/main/java/com/pakstr/app/signer/exceptions/SignerUnavailableException.kt +5 -0
- package/android-template/app/src/main/res/values/strings.xml +1 -1
- 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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
val appContext =
|
|
21
|
+
InstrumentationRegistry.getInstrumentation()
|
|
22
|
+
.targetContext
|
|
23
|
+
|
|
24
|
+
assertEquals(
|
|
25
|
+
"com.pakstr.app",
|
|
26
|
+
appContext.packageName
|
|
27
|
+
)
|
|
23
28
|
}
|
|
24
29
|
}
|
package/android-template/app/src/androidTest/java/com/pakstr/app/signer/NostrEventBuilderTest.kt
ADDED
|
@@ -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(
|
|
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
|
|
24
|
+
payload
|
|
25
|
+
? JSON.stringify(payload)
|
|
26
|
+
: ""
|
|
18
27
|
);
|
|
19
28
|
});
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
window.__nostrResolve = (id, result) => {
|
|
23
|
-
|
|
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 =
|
|
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(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
}
|