pakstr 0.20.0 → 0.21.1
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 +130 -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 +1 -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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package com.pakstr.app.crypto
|
|
2
|
+
|
|
3
|
+
import java.math.BigInteger
|
|
4
|
+
import java.util.Arrays
|
|
5
|
+
|
|
6
|
+
internal object Bip340Signer {
|
|
7
|
+
private const val AUXILIARY_TAG = "BIP0340/aux"
|
|
8
|
+
private const val NONCE_TAG = "BIP0340/nonce"
|
|
9
|
+
private const val CHALLENGE_TAG = "BIP0340/challenge"
|
|
10
|
+
|
|
11
|
+
fun sign(
|
|
12
|
+
privateKey: ByteArray,
|
|
13
|
+
message: ByteArray,
|
|
14
|
+
auxiliaryRandom: ByteArray
|
|
15
|
+
): ByteArray {
|
|
16
|
+
require(message.size == 32) {
|
|
17
|
+
"Message must be 32 bytes"
|
|
18
|
+
}
|
|
19
|
+
require(auxiliaryRandom.size == 32) {
|
|
20
|
+
"Auxiliary randomness must be 32 bytes"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
val originalScalar = Secp256k1.privateScalar(privateKey)
|
|
24
|
+
val publicPoint = Secp256k1.G.multiply(originalScalar).normalize()
|
|
25
|
+
val publicKey =
|
|
26
|
+
Secp256k1.fixedSizeBytes(
|
|
27
|
+
publicPoint.affineXCoord.toBigInteger()
|
|
28
|
+
)
|
|
29
|
+
val scalar =
|
|
30
|
+
if (publicPoint.affineYCoord.toBigInteger().testBit(0)) {
|
|
31
|
+
Secp256k1.N.subtract(originalScalar)
|
|
32
|
+
} else {
|
|
33
|
+
originalScalar
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
val scalarBytes = Secp256k1.fixedSizeBytes(scalar)
|
|
37
|
+
val auxiliaryHash =
|
|
38
|
+
Bip340Hash.taggedHash(
|
|
39
|
+
AUXILIARY_TAG,
|
|
40
|
+
auxiliaryRandom
|
|
41
|
+
)
|
|
42
|
+
val maskedScalar = ByteArray(32) { index ->
|
|
43
|
+
(scalarBytes[index].toInt() xor
|
|
44
|
+
auxiliaryHash[index].toInt()).toByte()
|
|
45
|
+
}
|
|
46
|
+
val nonceHash =
|
|
47
|
+
Bip340Hash.taggedHash(
|
|
48
|
+
NONCE_TAG,
|
|
49
|
+
maskedScalar,
|
|
50
|
+
publicKey,
|
|
51
|
+
message
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
var nonce =
|
|
56
|
+
BigInteger(1, nonceHash)
|
|
57
|
+
.mod(Secp256k1.N)
|
|
58
|
+
require(nonce.signum() != 0) {
|
|
59
|
+
"Invalid signing nonce"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
val noncePoint = Secp256k1.G.multiply(nonce).normalize()
|
|
63
|
+
if (noncePoint.affineYCoord.toBigInteger().testBit(0)) {
|
|
64
|
+
nonce = Secp256k1.N.subtract(nonce)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
val r =
|
|
68
|
+
Secp256k1.fixedSizeBytes(
|
|
69
|
+
noncePoint.affineXCoord.toBigInteger()
|
|
70
|
+
)
|
|
71
|
+
val challenge =
|
|
72
|
+
BigInteger(
|
|
73
|
+
1,
|
|
74
|
+
Bip340Hash.taggedHash(
|
|
75
|
+
CHALLENGE_TAG,
|
|
76
|
+
r,
|
|
77
|
+
publicKey,
|
|
78
|
+
message
|
|
79
|
+
)
|
|
80
|
+
).mod(Secp256k1.N)
|
|
81
|
+
val s =
|
|
82
|
+
nonce.add(challenge.multiply(scalar))
|
|
83
|
+
.mod(Secp256k1.N)
|
|
84
|
+
val signature = r + Secp256k1.fixedSizeBytes(s)
|
|
85
|
+
|
|
86
|
+
check(
|
|
87
|
+
Bip340Verifier.verify(
|
|
88
|
+
Bip340Hash.bytesToHex(publicKey),
|
|
89
|
+
Bip340Hash.bytesToHex(message),
|
|
90
|
+
Bip340Hash.bytesToHex(signature)
|
|
91
|
+
)
|
|
92
|
+
) {
|
|
93
|
+
"Generated signature failed verification"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return signature
|
|
97
|
+
} finally {
|
|
98
|
+
Arrays.fill(scalarBytes, 0)
|
|
99
|
+
Arrays.fill(auxiliaryHash, 0)
|
|
100
|
+
Arrays.fill(maskedScalar, 0)
|
|
101
|
+
Arrays.fill(nonceHash, 0)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
package com.pakstr.app.crypto
|
|
2
2
|
|
|
3
|
-
import com.pakstr.app.debug.AppDebugLogger
|
|
4
|
-
import org.bouncycastle.math.ec.ECPoint
|
|
5
3
|
import java.math.BigInteger
|
|
6
|
-
import java.security.MessageDigest
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
6
|
* Verifies BIP-340 Schnorr signatures over the secp256k1 curve.
|
|
@@ -24,6 +21,18 @@ object Bip340Verifier {
|
|
|
24
21
|
private const val CHALLENGE_TAG =
|
|
25
22
|
"BIP0340/challenge"
|
|
26
23
|
|
|
24
|
+
internal fun isValidXOnlyPubkey(
|
|
25
|
+
pubkeyHex: String
|
|
26
|
+
): Boolean {
|
|
27
|
+
return try {
|
|
28
|
+
val pubkey = Bip340Hash.hexToBytes(pubkeyHex)
|
|
29
|
+
|
|
30
|
+
pubkey.size == 32 && Secp256k1.liftX(pubkey).isValid
|
|
31
|
+
} catch (_: Exception) {
|
|
32
|
+
false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
/**
|
|
28
37
|
* Verifies a BIP-340 Schnorr signature.
|
|
29
38
|
*
|
|
@@ -42,13 +51,13 @@ object Bip340Verifier {
|
|
|
42
51
|
return try {
|
|
43
52
|
|
|
44
53
|
val pubkey =
|
|
45
|
-
hexToBytes(pubkeyHex)
|
|
54
|
+
Bip340Hash.hexToBytes(pubkeyHex)
|
|
46
55
|
|
|
47
56
|
val message =
|
|
48
|
-
hexToBytes(messageHex)
|
|
57
|
+
Bip340Hash.hexToBytes(messageHex)
|
|
49
58
|
|
|
50
59
|
val signature =
|
|
51
|
-
hexToBytes(signatureHex)
|
|
60
|
+
Bip340Hash.hexToBytes(signatureHex)
|
|
52
61
|
|
|
53
62
|
// BIP340 sizes
|
|
54
63
|
if (pubkey.size != 32)
|
|
@@ -89,18 +98,18 @@ object Bip340Verifier {
|
|
|
89
98
|
|
|
90
99
|
// Lift the x-only public key into a full secp256k1 point.
|
|
91
100
|
val P =
|
|
92
|
-
liftX(pubkey)
|
|
101
|
+
Secp256k1.liftX(pubkey)
|
|
93
102
|
|
|
94
103
|
// Compute the BIP-340 tagged challenge hash.
|
|
95
104
|
val eBytes =
|
|
96
|
-
taggedHash(
|
|
105
|
+
Bip340Hash.taggedHash(
|
|
97
106
|
CHALLENGE_TAG,
|
|
98
107
|
signature.copyOfRange(
|
|
99
108
|
0,
|
|
100
109
|
32
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
),
|
|
111
|
+
pubkey,
|
|
112
|
+
message
|
|
104
113
|
)
|
|
105
114
|
|
|
106
115
|
val e =
|
|
@@ -151,163 +160,4 @@ object Bip340Verifier {
|
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
|
|
154
|
-
/**
|
|
155
|
-
* Converts a BIP-340 x-only public key into a full secp256k1 curve point.
|
|
156
|
-
*
|
|
157
|
-
* BIP-340 uses x-only public keys, so the corresponding curve point is
|
|
158
|
-
* reconstructed by selecting the even Y coordinate.
|
|
159
|
-
*
|
|
160
|
-
* @throws IllegalArgumentException if the provided x coordinate does not
|
|
161
|
-
* represent a valid point on the secp256k1 curve.
|
|
162
|
-
*/
|
|
163
|
-
private fun liftX(
|
|
164
|
-
xBytes: ByteArray
|
|
165
|
-
): ECPoint {
|
|
166
|
-
|
|
167
|
-
val x =
|
|
168
|
-
BigInteger(
|
|
169
|
-
1,
|
|
170
|
-
xBytes
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
if (x >= Secp256k1.P) {
|
|
174
|
-
|
|
175
|
-
throw IllegalArgumentException(
|
|
176
|
-
"Invalid x coordinate"
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
val curve =
|
|
181
|
-
Secp256k1.CURVE.curve
|
|
182
|
-
|
|
183
|
-
val alpha =
|
|
184
|
-
x.modPow(
|
|
185
|
-
BigInteger.valueOf(3),
|
|
186
|
-
Secp256k1.P
|
|
187
|
-
)
|
|
188
|
-
.add(
|
|
189
|
-
curve.b.toBigInteger()
|
|
190
|
-
)
|
|
191
|
-
.mod(
|
|
192
|
-
Secp256k1.P
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
var y =
|
|
196
|
-
sqrtMod(
|
|
197
|
-
alpha,
|
|
198
|
-
Secp256k1.P
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
// verify square root exists
|
|
202
|
-
if (
|
|
203
|
-
y.multiply(y)
|
|
204
|
-
.mod(Secp256k1.P) != alpha
|
|
205
|
-
) {
|
|
206
|
-
|
|
207
|
-
throw IllegalArgumentException(
|
|
208
|
-
"Invalid public key"
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (y.testBit(0)) {
|
|
213
|
-
|
|
214
|
-
y = Secp256k1.P.subtract(y)
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
val point =
|
|
219
|
-
curve.createPoint(
|
|
220
|
-
x,
|
|
221
|
-
y
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
if (point.isInfinity) {
|
|
225
|
-
|
|
226
|
-
throw IllegalArgumentException(
|
|
227
|
-
"Point at infinity"
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (!point.isValid) {
|
|
232
|
-
|
|
233
|
-
throw IllegalArgumentException(
|
|
234
|
-
"Invalid curve point"
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
return point
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Calculates modular square root for secp256k1.
|
|
243
|
-
*
|
|
244
|
-
* Since secp256k1 prime satisfies p ≡ 3 (mod 4),
|
|
245
|
-
* the square root can be calculated as a^((p+1)/4) mod p.
|
|
246
|
-
*/
|
|
247
|
-
private fun sqrtMod(
|
|
248
|
-
a: BigInteger,
|
|
249
|
-
p: BigInteger
|
|
250
|
-
): BigInteger {
|
|
251
|
-
|
|
252
|
-
return a.modPow(
|
|
253
|
-
p.add(BigInteger.ONE)
|
|
254
|
-
.divide(
|
|
255
|
-
BigInteger.valueOf(4)
|
|
256
|
-
),
|
|
257
|
-
p
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private fun taggedHash(
|
|
262
|
-
tag: String,
|
|
263
|
-
msg: ByteArray
|
|
264
|
-
): ByteArray {
|
|
265
|
-
|
|
266
|
-
val tagHash =
|
|
267
|
-
sha256(
|
|
268
|
-
tag.toByteArray()
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
return sha256(
|
|
272
|
-
tagHash +
|
|
273
|
-
tagHash +
|
|
274
|
-
msg
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private fun sha256(
|
|
280
|
-
data: ByteArray
|
|
281
|
-
): ByteArray {
|
|
282
|
-
|
|
283
|
-
return MessageDigest
|
|
284
|
-
.getInstance(
|
|
285
|
-
"SHA-256"
|
|
286
|
-
)
|
|
287
|
-
.digest(
|
|
288
|
-
data
|
|
289
|
-
)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
private fun hexToBytes(
|
|
293
|
-
hex: String
|
|
294
|
-
): ByteArray {
|
|
295
|
-
|
|
296
|
-
require(
|
|
297
|
-
hex.length % 2 == 0
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
return ByteArray(
|
|
301
|
-
hex.length / 2
|
|
302
|
-
) { index ->
|
|
303
|
-
|
|
304
|
-
hex.substring(
|
|
305
|
-
index * 2,
|
|
306
|
-
index * 2 + 2
|
|
307
|
-
)
|
|
308
|
-
.toInt(16)
|
|
309
|
-
.toByte()
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
163
|
}
|