pakstr 0.1.0 → 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.
- package/android-template/app/src/main/assets/nip07.js +66 -25
- package/android-template/app/src/main/assets/www/index.html +60 -65
- package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +26 -5
- package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +4 -2
- package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip04Crypto.kt +33 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEncryption.kt +16 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/AmberSigner.kt +347 -36
- package/android-template/app/src/main/java/com/pakstr/app/signer/BunkerSigner.kt +14 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/FakeSigner.kt +14 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +189 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +22 -0
- package/android-template/app/src/main/java/com/pakstr/app/signer/interfaces/NostrSigner.kt +10 -0
- package/package.json +1 -1
|
@@ -5,71 +5,112 @@
|
|
|
5
5
|
const pending = new Map();
|
|
6
6
|
|
|
7
7
|
function callNative(method, payload) {
|
|
8
|
-
|
|
9
8
|
return new Promise((resolve, reject) => {
|
|
10
9
|
|
|
11
10
|
const id = crypto.randomUUID();
|
|
12
11
|
|
|
13
|
-
pending.set(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
reject
|
|
18
|
-
}
|
|
19
|
-
);
|
|
12
|
+
pending.set(id, {
|
|
13
|
+
resolve,
|
|
14
|
+
reject
|
|
15
|
+
});
|
|
20
16
|
|
|
21
17
|
window.AndroidBridge.call(
|
|
22
18
|
method,
|
|
23
19
|
id,
|
|
24
|
-
payload
|
|
25
|
-
? JSON.stringify(payload)
|
|
26
|
-
: ""
|
|
20
|
+
payload ? JSON.stringify(payload) : ""
|
|
27
21
|
);
|
|
28
22
|
});
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
window.__nostrResolve = (id, result) => {
|
|
32
26
|
|
|
33
|
-
const p =
|
|
34
|
-
pending.get(id);
|
|
35
|
-
|
|
27
|
+
const p = pending.get(id);
|
|
36
28
|
if (!p) return;
|
|
37
29
|
|
|
38
30
|
pending.delete(id);
|
|
39
31
|
|
|
40
32
|
try {
|
|
41
|
-
|
|
42
|
-
const parsed =
|
|
43
|
-
JSON.parse(result);
|
|
33
|
+
const parsed = JSON.parse(result);
|
|
44
34
|
|
|
45
35
|
if (parsed.error) {
|
|
46
36
|
p.reject(parsed);
|
|
47
37
|
return;
|
|
48
38
|
}
|
|
39
|
+
|
|
49
40
|
} catch (_) {
|
|
50
41
|
// Normal string response
|
|
51
42
|
}
|
|
43
|
+
|
|
52
44
|
p.resolve(result);
|
|
53
45
|
};
|
|
54
46
|
|
|
55
47
|
window.nostr = {
|
|
56
48
|
|
|
57
49
|
getPublicKey: async () => {
|
|
50
|
+
return await callNative("getPublicKey");
|
|
51
|
+
},
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
signEvent: async (event) => {
|
|
54
|
+
|
|
55
|
+
const result = await callNative(
|
|
56
|
+
"signEvent",
|
|
57
|
+
event
|
|
61
58
|
);
|
|
59
|
+
|
|
60
|
+
return JSON.parse(result);
|
|
62
61
|
},
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
nip04: {
|
|
64
|
+
|
|
65
|
+
encrypt: async (pubkey, plaintext) => {
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
return await callNative(
|
|
68
|
+
"nip04_encrypt",
|
|
69
|
+
{
|
|
70
|
+
pubkey,
|
|
71
|
+
plaintext
|
|
72
|
+
}
|
|
70
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
|
+
}
|
|
71
112
|
|
|
72
|
-
return JSON.parse(result);
|
|
73
113
|
}
|
|
74
114
|
};
|
|
115
|
+
|
|
75
116
|
})();
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
Sign Event
|
|
40
40
|
</button>
|
|
41
41
|
|
|
42
|
-
|
|
43
42
|
<pre id="output">
|
|
44
43
|
Waiting...
|
|
45
44
|
</pre>
|
|
@@ -47,6 +46,7 @@ Waiting...
|
|
|
47
46
|
|
|
48
47
|
<script>
|
|
49
48
|
|
|
49
|
+
|
|
50
50
|
function log(data) {
|
|
51
51
|
|
|
52
52
|
document.getElementById("output").innerText =
|
|
@@ -59,10 +59,16 @@ Waiting...
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
// Check provider after Pakstr injection
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
|
|
65
|
+
console.log(
|
|
66
|
+
"Nostr provider:",
|
|
67
|
+
window.nostr
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
}, 2000);
|
|
71
|
+
|
|
66
72
|
|
|
67
73
|
|
|
68
74
|
async function getPublicKey() {
|
|
@@ -91,7 +97,8 @@ Waiting...
|
|
|
91
97
|
|
|
92
98
|
pubkey,
|
|
93
99
|
|
|
94
|
-
length:
|
|
100
|
+
length:
|
|
101
|
+
pubkey.length,
|
|
95
102
|
|
|
96
103
|
format:
|
|
97
104
|
pubkey.startsWith("npub")
|
|
@@ -105,7 +112,8 @@ Waiting...
|
|
|
105
112
|
|
|
106
113
|
log({
|
|
107
114
|
|
|
108
|
-
error:
|
|
115
|
+
error:
|
|
116
|
+
e.message
|
|
109
117
|
|
|
110
118
|
});
|
|
111
119
|
|
|
@@ -115,102 +123,89 @@ Waiting...
|
|
|
115
123
|
|
|
116
124
|
|
|
117
125
|
|
|
118
|
-
async function signEvent() {
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
|
|
122
|
-
log("Signing event...");
|
|
123
|
-
|
|
124
|
-
if (!window.nostr) {
|
|
125
|
-
|
|
126
|
-
throw new Error(
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const pubkey =
|
|
135
|
-
|
|
136
|
-
await window.nostr.getPublicKey();
|
|
137
|
-
|
|
138
|
-
const event = {
|
|
127
|
+
async function signEvent() {
|
|
139
128
|
|
|
140
|
-
|
|
129
|
+
try {
|
|
141
130
|
|
|
142
|
-
|
|
131
|
+
log("Signing event...");
|
|
143
132
|
|
|
144
|
-
content:
|
|
145
133
|
|
|
146
|
-
|
|
134
|
+
if (!window.nostr) {
|
|
147
135
|
|
|
148
|
-
|
|
136
|
+
throw new Error(
|
|
137
|
+
"Nostr provider not available"
|
|
138
|
+
);
|
|
149
139
|
|
|
150
|
-
|
|
140
|
+
}
|
|
151
141
|
|
|
152
|
-
Math.floor(
|
|
153
142
|
|
|
154
|
-
|
|
143
|
+
const pubkey =
|
|
144
|
+
await window.nostr.getPublicKey();
|
|
155
145
|
|
|
156
|
-
)
|
|
157
146
|
|
|
158
|
-
|
|
147
|
+
const event = {
|
|
159
148
|
|
|
160
|
-
|
|
149
|
+
pubkey,
|
|
161
150
|
|
|
162
|
-
|
|
151
|
+
kind: 1,
|
|
163
152
|
|
|
164
|
-
|
|
153
|
+
content:
|
|
154
|
+
"Hello from Pakstr NIP-07",
|
|
165
155
|
|
|
166
|
-
|
|
156
|
+
tags: [],
|
|
167
157
|
|
|
168
|
-
|
|
158
|
+
created_at:
|
|
159
|
+
Math.floor(
|
|
160
|
+
Date.now() / 1000
|
|
161
|
+
)
|
|
169
162
|
|
|
170
|
-
|
|
163
|
+
};
|
|
171
164
|
|
|
172
|
-
event
|
|
173
165
|
|
|
174
|
-
|
|
166
|
+
console.log(
|
|
167
|
+
"Unsigned event",
|
|
168
|
+
event
|
|
169
|
+
);
|
|
175
170
|
|
|
176
|
-
const signedEvent =
|
|
177
171
|
|
|
178
|
-
|
|
172
|
+
const signedEvent =
|
|
173
|
+
await window.nostr.signEvent(
|
|
174
|
+
event
|
|
175
|
+
);
|
|
179
176
|
|
|
180
|
-
event
|
|
181
177
|
|
|
178
|
+
console.log(
|
|
179
|
+
"Signed event",
|
|
180
|
+
signedEvent
|
|
182
181
|
);
|
|
183
182
|
|
|
184
|
-
console.log(
|
|
185
183
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
signedEvent
|
|
189
|
-
|
|
190
|
-
);
|
|
184
|
+
log({
|
|
191
185
|
|
|
192
|
-
|
|
186
|
+
success: true,
|
|
193
187
|
|
|
194
|
-
|
|
188
|
+
signedEvent
|
|
195
189
|
|
|
196
|
-
|
|
190
|
+
});
|
|
197
191
|
|
|
198
|
-
});
|
|
199
192
|
|
|
200
|
-
|
|
193
|
+
} catch (e) {
|
|
201
194
|
|
|
202
|
-
|
|
195
|
+
console.error(e);
|
|
203
196
|
|
|
204
|
-
log({
|
|
205
197
|
|
|
206
|
-
|
|
198
|
+
log({
|
|
207
199
|
|
|
208
|
-
|
|
200
|
+
error:
|
|
201
|
+
e.message,
|
|
209
202
|
|
|
210
|
-
|
|
203
|
+
stack:
|
|
204
|
+
e.stack
|
|
211
205
|
|
|
212
|
-
|
|
206
|
+
});
|
|
213
207
|
|
|
208
|
+
}
|
|
214
209
|
}
|
|
215
210
|
|
|
216
211
|
|
|
@@ -3,6 +3,7 @@ package com.pakstr.app
|
|
|
3
3
|
import android.Manifest
|
|
4
4
|
import android.content.Intent
|
|
5
5
|
import android.os.Bundle
|
|
6
|
+
import android.util.Log
|
|
6
7
|
|
|
7
8
|
import android.view.View
|
|
8
9
|
import android.webkit.PermissionRequest
|
|
@@ -17,12 +18,17 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
|
17
18
|
import androidx.core.view.ViewCompat
|
|
18
19
|
import androidx.core.view.WindowInsetsCompat
|
|
19
20
|
import androidx.core.view.updatePadding
|
|
21
|
+
import androidx.lifecycle.lifecycleScope
|
|
20
22
|
import com.pakstr.app.permissions.PermissionManager
|
|
21
23
|
import com.pakstr.app.permissions.interfaces.PermissionHandler
|
|
22
24
|
import com.pakstr.app.crypto.CryptoInitializer
|
|
25
|
+
import com.pakstr.app.debug.AppDebugLogger
|
|
26
|
+
|
|
23
27
|
import com.pakstr.app.signer.SignerManager
|
|
28
|
+
import com.pakstr.app.signer.SignerType
|
|
24
29
|
import com.pakstr.app.utils.AppPreferences
|
|
25
30
|
import com.pakstr.app.utils.SignerPickerBottomSheet
|
|
31
|
+
import kotlinx.coroutines.launch
|
|
26
32
|
|
|
27
33
|
class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
28
34
|
|
|
@@ -44,6 +50,12 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
44
50
|
|
|
45
51
|
super.onCreate(savedInstanceState)
|
|
46
52
|
|
|
53
|
+
if (BuildConfig.DEBUG) {
|
|
54
|
+
|
|
55
|
+
WebView.setWebContentsDebuggingEnabled(true)
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
setContentView(
|
|
48
60
|
R.layout.activity_layout
|
|
49
61
|
)
|
|
@@ -58,10 +70,12 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
initSigner()
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
signerManager.setActiveType(
|
|
74
|
+
SignerType.AMBER
|
|
75
|
+
)
|
|
76
|
+
//if (AppPreferences.getBoolean("isFirstRun", true)) {
|
|
77
|
+
//showSignerPicker()
|
|
78
|
+
//}
|
|
65
79
|
|
|
66
80
|
ViewCompat.setOnApplyWindowInsetsListener(
|
|
67
81
|
container
|
|
@@ -144,8 +158,15 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
144
158
|
}
|
|
145
159
|
|
|
146
160
|
override fun onDestroy() {
|
|
147
|
-
|
|
161
|
+
|
|
162
|
+
if (::runtime.isInitialized) {
|
|
163
|
+
|
|
164
|
+
runtime.stop()
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
148
168
|
super.onDestroy()
|
|
169
|
+
|
|
149
170
|
}
|
|
150
171
|
|
|
151
172
|
override fun onRequestPermissionsResult(
|
|
@@ -110,6 +110,8 @@ class WebViewController(
|
|
|
110
110
|
val url = request?.url ?: return true
|
|
111
111
|
return url.host != "127.0.0.1"
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
|
|
113
115
|
}
|
|
114
116
|
webChromeClient = object : WebChromeClient() {
|
|
115
117
|
|
|
@@ -129,7 +131,7 @@ class WebViewController(
|
|
|
129
131
|
|
|
130
132
|
signerManager = signerManager,
|
|
131
133
|
|
|
132
|
-
webView = webView
|
|
134
|
+
webView = webView,
|
|
133
135
|
|
|
134
136
|
),
|
|
135
137
|
|
|
@@ -192,4 +194,4 @@ class WebViewController(
|
|
|
192
194
|
false
|
|
193
195
|
}
|
|
194
196
|
}
|
|
195
|
-
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.pakstr.app.crypto
|
|
2
|
+
|
|
3
|
+
class Nip04Crypto : NostrEncryption {
|
|
4
|
+
|
|
5
|
+
override fun encrypt(
|
|
6
|
+
|
|
7
|
+
plaintext: String,
|
|
8
|
+
|
|
9
|
+
privateKey: ByteArray,
|
|
10
|
+
|
|
11
|
+
publicKey: ByteArray
|
|
12
|
+
|
|
13
|
+
): String {
|
|
14
|
+
|
|
15
|
+
TODO()
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun decrypt(
|
|
20
|
+
|
|
21
|
+
payload: String,
|
|
22
|
+
|
|
23
|
+
privateKey: ByteArray,
|
|
24
|
+
|
|
25
|
+
publicKey: ByteArray
|
|
26
|
+
|
|
27
|
+
): String {
|
|
28
|
+
|
|
29
|
+
TODO()
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.pakstr.app.crypto
|
|
2
|
+
|
|
3
|
+
interface NostrEncryption {
|
|
4
|
+
|
|
5
|
+
fun encrypt(
|
|
6
|
+
plaintext: String,
|
|
7
|
+
privateKey: ByteArray,
|
|
8
|
+
publicKey: ByteArray
|
|
9
|
+
): String
|
|
10
|
+
|
|
11
|
+
fun decrypt(
|
|
12
|
+
payload: String,
|
|
13
|
+
privateKey: ByteArray,
|
|
14
|
+
publicKey: ByteArray
|
|
15
|
+
): String
|
|
16
|
+
}
|
|
@@ -42,47 +42,77 @@ class AmberSigner(
|
|
|
42
42
|
// Android intents return results asynchronously, so the callback is used
|
|
43
43
|
// to resume the suspended coroutine when Amber responds.
|
|
44
44
|
private var pendingCallback: ((String) -> Unit)? = null
|
|
45
|
+
private var cachedPubkey: String? = null
|
|
46
|
+
override suspend fun getPublicKey(): String {
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
cachedPubkey?.let {
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
return it
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return withTimeout(30_000) {
|
|
55
|
+
|
|
56
|
+
suspendCancellableCoroutine { continuation ->
|
|
57
|
+
|
|
58
|
+
pendingCallback = { pubkey ->
|
|
59
|
+
|
|
60
|
+
cachedPubkey = pubkey
|
|
61
|
+
|
|
62
|
+
if (continuation.isActive) {
|
|
63
|
+
|
|
64
|
+
continuation.resume(pubkey)
|
|
65
|
+
|
|
66
|
+
}
|
|
51
67
|
|
|
52
|
-
if (continuation.isActive) {
|
|
53
|
-
continuation.resume(pubkey)
|
|
54
68
|
}
|
|
55
|
-
}
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
Intent.ACTION_VIEW
|
|
59
|
-
).apply {
|
|
70
|
+
val intent = Intent(
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
"nostrsigner:?type=get_public_key"
|
|
63
|
-
)
|
|
72
|
+
Intent.ACTION_VIEW
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
"type", "get_public_key"
|
|
67
|
-
)
|
|
74
|
+
).apply {
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
data = Uri.parse(
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
"nostrsigner:?type=get_public_key"
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
)
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
putExtra(
|
|
83
|
+
|
|
84
|
+
"type",
|
|
85
|
+
|
|
86
|
+
"get_public_key"
|
|
87
|
+
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
|
|
94
|
+
launcher.launch(intent)
|
|
95
|
+
|
|
96
|
+
} catch (_: ActivityNotFoundException) {
|
|
97
|
+
|
|
98
|
+
if (continuation.isActive) {
|
|
99
|
+
|
|
100
|
+
continuation.resume("")
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
continuation.invokeOnCancellation {
|
|
107
|
+
|
|
108
|
+
pendingCallback = null
|
|
76
109
|
|
|
77
|
-
if (continuation.isActive) {
|
|
78
|
-
continuation.resume("")
|
|
79
110
|
}
|
|
80
|
-
}
|
|
81
111
|
|
|
82
|
-
continuation.invokeOnCancellation {
|
|
83
|
-
pendingCallback = null
|
|
84
112
|
}
|
|
113
|
+
|
|
85
114
|
}
|
|
115
|
+
|
|
86
116
|
}
|
|
87
117
|
|
|
88
118
|
override suspend fun signEvent(
|
|
@@ -165,23 +195,270 @@ class AmberSigner(
|
|
|
165
195
|
}
|
|
166
196
|
|
|
167
197
|
override suspend fun nip04Encrypt(
|
|
168
|
-
pubkey: String,
|
|
169
|
-
|
|
198
|
+
pubkey: String,
|
|
199
|
+
plaintext: String
|
|
200
|
+
): String =
|
|
201
|
+
withTimeout(30_000) {
|
|
170
202
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
203
|
+
suspendCancellableCoroutine { continuation ->
|
|
204
|
+
|
|
205
|
+
pendingCallback = { encrypted ->
|
|
206
|
+
|
|
207
|
+
if (continuation.isActive) {
|
|
208
|
+
continuation.resume(encrypted)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
val intent = Intent(
|
|
213
|
+
Intent.ACTION_VIEW
|
|
214
|
+
).apply {
|
|
215
|
+
|
|
216
|
+
data = Uri.parse(
|
|
217
|
+
"nostrsigner:?type=nip04_encrypt" +
|
|
218
|
+
"&pubkey=$pubkey" +
|
|
219
|
+
"&plaintext=${Uri.encode(plaintext)}"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
putExtra(
|
|
223
|
+
"type",
|
|
224
|
+
"nip04_encrypt"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
putExtra(
|
|
228
|
+
"pubkey",
|
|
229
|
+
pubkey
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
putExtra(
|
|
233
|
+
"plaintext",
|
|
234
|
+
plaintext
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
|
|
240
|
+
launcher.launch(intent)
|
|
241
|
+
|
|
242
|
+
} catch (_: ActivityNotFoundException) {
|
|
243
|
+
|
|
244
|
+
if (continuation.isActive) {
|
|
245
|
+
continuation.resume("")
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
continuation.invokeOnCancellation {
|
|
250
|
+
pendingCallback = null
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
175
254
|
|
|
176
255
|
override suspend fun nip04Decrypt(
|
|
177
|
-
pubkey: String,
|
|
178
|
-
|
|
256
|
+
pubkey: String,
|
|
257
|
+
ciphertext: String
|
|
258
|
+
): String = withTimeout(30_000) {
|
|
179
259
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
260
|
+
suspendCancellableCoroutine { continuation ->
|
|
261
|
+
|
|
262
|
+
pendingCallback = { result ->
|
|
263
|
+
|
|
264
|
+
if (continuation.isActive) {
|
|
265
|
+
continuation.resume(result)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
val intent = Intent(
|
|
270
|
+
Intent.ACTION_VIEW
|
|
271
|
+
).apply {
|
|
272
|
+
|
|
273
|
+
data = Uri.parse(
|
|
274
|
+
"nostrsigner:?type=nip04_decrypt" +
|
|
275
|
+
"&pubkey=$pubkey" +
|
|
276
|
+
"&message=${Uri.encode(ciphertext)}"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
putExtra(
|
|
280
|
+
"type",
|
|
281
|
+
"nip04_decrypt"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
putExtra(
|
|
285
|
+
"pubkey",
|
|
286
|
+
pubkey
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
putExtra(
|
|
290
|
+
"message",
|
|
291
|
+
ciphertext
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
|
|
297
|
+
launcher.launch(intent)
|
|
298
|
+
|
|
299
|
+
} catch (_: ActivityNotFoundException) {
|
|
300
|
+
|
|
301
|
+
if (continuation.isActive) {
|
|
302
|
+
continuation.resume("")
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
continuation.invokeOnCancellation {
|
|
307
|
+
pendingCallback = null
|
|
308
|
+
}
|
|
309
|
+
}
|
|
183
310
|
}
|
|
184
311
|
|
|
312
|
+
override suspend fun nip44Encrypt(
|
|
313
|
+
|
|
314
|
+
pubkey: String,
|
|
315
|
+
|
|
316
|
+
plaintext: String
|
|
317
|
+
|
|
318
|
+
): String =
|
|
319
|
+
|
|
320
|
+
withTimeout(30_000) {
|
|
321
|
+
|
|
322
|
+
val currentUser = getCachedPublicKey()
|
|
323
|
+
|
|
324
|
+
suspendCancellableCoroutine { continuation ->
|
|
325
|
+
|
|
326
|
+
pendingCallback = { encrypted ->
|
|
327
|
+
|
|
328
|
+
if (continuation.isActive) {
|
|
329
|
+
|
|
330
|
+
continuation.resume(encrypted)
|
|
331
|
+
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
val intent = Intent(
|
|
337
|
+
|
|
338
|
+
Intent.ACTION_VIEW
|
|
339
|
+
|
|
340
|
+
).apply {
|
|
341
|
+
|
|
342
|
+
data = Uri.parse(
|
|
343
|
+
|
|
344
|
+
"nostrsigner:${Uri.encode(plaintext)}"
|
|
345
|
+
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
putExtra(
|
|
349
|
+
|
|
350
|
+
"type",
|
|
351
|
+
|
|
352
|
+
"nip44_encrypt"
|
|
353
|
+
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
putExtra(
|
|
357
|
+
|
|
358
|
+
"pubkey",
|
|
359
|
+
|
|
360
|
+
pubkey
|
|
361
|
+
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
putExtra(
|
|
365
|
+
|
|
366
|
+
"current_user",
|
|
367
|
+
|
|
368
|
+
currentUser
|
|
369
|
+
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
|
|
376
|
+
launcher.launch(intent)
|
|
377
|
+
|
|
378
|
+
} catch (_: ActivityNotFoundException) {
|
|
379
|
+
|
|
380
|
+
if (continuation.isActive) {
|
|
381
|
+
|
|
382
|
+
continuation.resume("")
|
|
383
|
+
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
continuation.invokeOnCancellation {
|
|
389
|
+
|
|
390
|
+
pendingCallback = null
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
override suspend fun nip44Decrypt(
|
|
399
|
+
pubkey: String,
|
|
400
|
+
ciphertext: String
|
|
401
|
+
): String =
|
|
402
|
+
withTimeout(30_000) {
|
|
403
|
+
val currentUser = getCachedPublicKey()
|
|
404
|
+
suspendCancellableCoroutine { continuation ->
|
|
405
|
+
|
|
406
|
+
pendingCallback = { result ->
|
|
407
|
+
|
|
408
|
+
if (continuation.isActive) {
|
|
409
|
+
continuation.resume(result)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
val intent = Intent(
|
|
414
|
+
Intent.ACTION_VIEW
|
|
415
|
+
).apply {
|
|
416
|
+
|
|
417
|
+
data = Uri.parse(
|
|
418
|
+
"nostrsigner:${Uri.encode(ciphertext)}"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
putExtra(
|
|
422
|
+
"type",
|
|
423
|
+
"nip44_decrypt"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
putExtra(
|
|
427
|
+
"pubkey",
|
|
428
|
+
pubkey
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
putExtra(
|
|
432
|
+
"current_user",
|
|
433
|
+
currentUser
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
AppDebugLogger.log(
|
|
439
|
+
"NIP44",
|
|
440
|
+
"decrypt sender=$pubkey current=$pubkey length=${ciphertext.length}"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
|
|
446
|
+
launcher.launch(intent)
|
|
447
|
+
|
|
448
|
+
} catch (_: ActivityNotFoundException) {
|
|
449
|
+
|
|
450
|
+
if (continuation.isActive) {
|
|
451
|
+
continuation.resume("")
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
continuation.invokeOnCancellation {
|
|
457
|
+
pendingCallback = null
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
185
462
|
fun onResult(
|
|
186
463
|
result: ActivityResult
|
|
187
464
|
) {
|
|
@@ -200,10 +477,44 @@ class AmberSigner(
|
|
|
200
477
|
|
|
201
478
|
val value =
|
|
202
479
|
|
|
203
|
-
result.data?.getStringExtra("result")
|
|
480
|
+
result.data?.getStringExtra("result")
|
|
481
|
+
|
|
482
|
+
?: result.data?.getStringExtra("event")
|
|
483
|
+
|
|
484
|
+
?: result.data?.getStringExtra("signature")
|
|
485
|
+
|
|
486
|
+
?: result.data?.getStringExtra("plaintext")
|
|
487
|
+
|
|
488
|
+
?: result.data?.getStringExtra("message")
|
|
489
|
+
?: result.data?.getStringExtra("content")
|
|
490
|
+
|
|
491
|
+
?: ""
|
|
492
|
+
|
|
493
|
+
AppDebugLogger.log(
|
|
494
|
+
|
|
495
|
+
"AMBER_RESULT",
|
|
496
|
+
|
|
497
|
+
value
|
|
498
|
+
|
|
499
|
+
)
|
|
500
|
+
AppDebugLogger.log(
|
|
501
|
+
|
|
502
|
+
"AMBER_RESULT_KEYS",
|
|
503
|
+
|
|
504
|
+
result.data?.extras?.keySet()?.joinToString()
|
|
505
|
+
|
|
506
|
+
?: "NO_KEYS"
|
|
507
|
+
|
|
508
|
+
)
|
|
204
509
|
|
|
205
510
|
callback?.invoke(
|
|
206
511
|
value
|
|
207
512
|
)
|
|
208
513
|
}
|
|
514
|
+
|
|
515
|
+
private suspend fun getCachedPublicKey(): String {
|
|
516
|
+
|
|
517
|
+
return cachedPubkey ?: getPublicKey()
|
|
518
|
+
|
|
519
|
+
}
|
|
209
520
|
}
|
|
@@ -25,4 +25,18 @@ class BunkerSigner(context: Context) : NostrSigner {
|
|
|
25
25
|
): String {
|
|
26
26
|
TODO("Not yet implemented")
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
override suspend fun nip44Encrypt(
|
|
30
|
+
pubkey: String,
|
|
31
|
+
plaintext: String
|
|
32
|
+
): String {
|
|
33
|
+
TODO("Not yet implemented")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override suspend fun nip44Decrypt(
|
|
37
|
+
pubkey: String,
|
|
38
|
+
ciphertext: String
|
|
39
|
+
): String {
|
|
40
|
+
TODO("Not yet implemented")
|
|
41
|
+
}
|
|
28
42
|
}
|
|
@@ -28,4 +28,18 @@ class FakeSigner : NostrSigner {
|
|
|
28
28
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
override suspend fun nip44Encrypt(
|
|
32
|
+
pubkey: String,
|
|
33
|
+
plaintext: String
|
|
34
|
+
): String {
|
|
35
|
+
TODO("Not yet implemented")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override suspend fun nip44Decrypt(
|
|
39
|
+
pubkey: String,
|
|
40
|
+
ciphertext: String
|
|
41
|
+
): String {
|
|
42
|
+
TODO("Not yet implemented")
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
}
|
|
@@ -114,6 +114,195 @@ class NostrBridge(
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
"nip04_encrypt" -> {
|
|
118
|
+
|
|
119
|
+
scope.launch {
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
|
|
123
|
+
require(!payload.isNullOrBlank()) {
|
|
124
|
+
"NIP04 encrypt payload is empty"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
val json =
|
|
128
|
+
JSONObject(payload)
|
|
129
|
+
|
|
130
|
+
val pubkey =
|
|
131
|
+
json.getString("pubkey")
|
|
132
|
+
|
|
133
|
+
val plaintext =
|
|
134
|
+
json.getString("plaintext")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
val result =
|
|
138
|
+
signerManager
|
|
139
|
+
.getSigner()
|
|
140
|
+
.nip04Encrypt(
|
|
141
|
+
pubkey,
|
|
142
|
+
plaintext
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
resolve(
|
|
147
|
+
callbackId,
|
|
148
|
+
result
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
} catch (e: SignerUnavailableException) {
|
|
152
|
+
|
|
153
|
+
resolve(
|
|
154
|
+
callbackId,
|
|
155
|
+
errorResponse(
|
|
156
|
+
"SIGNER_UNAVAILABLE",
|
|
157
|
+
e.message ?: "Signer unavailable"
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
} catch (e: Exception) {
|
|
162
|
+
|
|
163
|
+
resolve(
|
|
164
|
+
callbackId,
|
|
165
|
+
errorResponse(
|
|
166
|
+
"SIGNER_ERROR",
|
|
167
|
+
e.message ?: "Unknown signer error"
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
"nip04_decrypt" -> {
|
|
176
|
+
|
|
177
|
+
scope.launch {
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
|
|
181
|
+
require(!payload.isNullOrBlank()) {
|
|
182
|
+
"NIP04 decrypt payload is empty"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
val json =
|
|
187
|
+
JSONObject(payload)
|
|
188
|
+
|
|
189
|
+
val pubkey =
|
|
190
|
+
json.getString("pubkey")
|
|
191
|
+
|
|
192
|
+
val ciphertext =
|
|
193
|
+
json.getString("ciphertext")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
val result =
|
|
197
|
+
signerManager
|
|
198
|
+
.getSigner()
|
|
199
|
+
.nip04Decrypt(
|
|
200
|
+
pubkey,
|
|
201
|
+
ciphertext
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
resolve(
|
|
206
|
+
callbackId,
|
|
207
|
+
result
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
} catch (e: SignerUnavailableException) {
|
|
212
|
+
|
|
213
|
+
resolve(
|
|
214
|
+
callbackId,
|
|
215
|
+
errorResponse(
|
|
216
|
+
"SIGNER_UNAVAILABLE",
|
|
217
|
+
e.message ?: "Signer unavailable"
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
} catch (e: Exception) {
|
|
222
|
+
|
|
223
|
+
resolve(
|
|
224
|
+
callbackId,
|
|
225
|
+
errorResponse(
|
|
226
|
+
"SIGNER_ERROR",
|
|
227
|
+
e.message ?: "Unknown signer error"
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
"nip44_encrypt" -> {
|
|
235
|
+
|
|
236
|
+
scope.launch {
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
|
|
240
|
+
val json =
|
|
241
|
+
JSONObject(payload ?: "")
|
|
242
|
+
|
|
243
|
+
val result =
|
|
244
|
+
signerManager
|
|
245
|
+
.getSigner()
|
|
246
|
+
.nip44Encrypt(
|
|
247
|
+
json.getString("pubkey"),
|
|
248
|
+
json.getString("plaintext")
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
resolve(
|
|
252
|
+
callbackId,
|
|
253
|
+
result
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
} catch (e: Exception) {
|
|
258
|
+
|
|
259
|
+
resolve(
|
|
260
|
+
callbackId,
|
|
261
|
+
errorResponse(
|
|
262
|
+
"SIGNER_ERROR",
|
|
263
|
+
e.message ?: ""
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
"nip44_decrypt" -> {
|
|
271
|
+
|
|
272
|
+
scope.launch {
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
|
|
276
|
+
val json =
|
|
277
|
+
JSONObject(payload ?: "")
|
|
278
|
+
|
|
279
|
+
val result =
|
|
280
|
+
signerManager
|
|
281
|
+
.getSigner()
|
|
282
|
+
.nip44Decrypt(
|
|
283
|
+
json.getString("pubkey"),
|
|
284
|
+
json.getString("ciphertext")
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
resolve(
|
|
288
|
+
callbackId,
|
|
289
|
+
result
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
} catch (e: Exception) {
|
|
294
|
+
|
|
295
|
+
resolve(
|
|
296
|
+
callbackId,
|
|
297
|
+
errorResponse(
|
|
298
|
+
"SIGNER_ERROR",
|
|
299
|
+
e.message ?: ""
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
117
306
|
else -> {
|
|
118
307
|
|
|
119
308
|
resolve(
|
|
@@ -44,6 +44,8 @@ class SignerManager(
|
|
|
44
44
|
|
|
45
45
|
private var activeSigner: NostrSigner? = null
|
|
46
46
|
|
|
47
|
+
private var currentUserPubkey: String? = null
|
|
48
|
+
|
|
47
49
|
fun getActiveType(): SignerType? {
|
|
48
50
|
|
|
49
51
|
val value =
|
|
@@ -60,6 +62,26 @@ class SignerManager(
|
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
suspend fun getCurrentUserPubkey(): String {
|
|
66
|
+
|
|
67
|
+
currentUserPubkey?.let {
|
|
68
|
+
|
|
69
|
+
return it
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
val pk =
|
|
74
|
+
|
|
75
|
+
getSigner()
|
|
76
|
+
|
|
77
|
+
.getPublicKey()
|
|
78
|
+
|
|
79
|
+
currentUserPubkey = pk
|
|
80
|
+
|
|
81
|
+
return pk
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
63
85
|
fun setActiveType(
|
|
64
86
|
type: SignerType
|
|
65
87
|
) {
|