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.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/android-template/app/build.gradle.kts +4 -0
  3. package/android-template/app/src/androidTest/java/com/pakstr/app/signer/SignerManagerTest.kt +273 -0
  4. package/android-template/app/src/main/assets/www/index.html +0 -6
  5. package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +20 -4
  6. package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +130 -12
  7. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Hash.kt +56 -0
  8. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Signer.kt +104 -0
  9. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Verifier.kt +20 -170
  10. package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip44Crypto.kt +513 -0
  11. package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip46ClientKey.kt +140 -0
  12. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventHasher.kt +3 -28
  13. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventSerializer.kt +74 -0
  14. package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +100 -0
  15. package/android-template/app/src/main/java/com/pakstr/app/signer/BunkerSigner.kt +32 -7
  16. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +36 -9
  17. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +240 -163
  18. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/BunkerConfig.kt +175 -0
  19. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/BunkerConnectionStore.kt +144 -0
  20. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Client.kt +79 -0
  21. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Models.kt +319 -0
  22. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingClient.kt +159 -0
  23. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingQrCode.kt +35 -0
  24. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46PairingUri.kt +104 -0
  25. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46Protocol.kt +695 -0
  26. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/Nip46RelayTransport.kt +489 -0
  27. package/android-template/app/src/main/java/com/pakstr/app/signer/bunker/RelayNip46Client.kt +403 -0
  28. package/android-template/app/src/main/java/com/pakstr/app/utils/BunkerPairingDialog.kt +82 -0
  29. package/android-template/app/src/main/java/com/pakstr/app/utils/SignerPickerBottomSheet.kt +78 -17
  30. package/android-template/app/src/main/res/layout/activity_layout.xml +1 -0
  31. package/android-template/app/src/main/res/layout/dialog_bunker_pairing.xml +75 -0
  32. package/android-template/app/src/main/res/layout/dialog_picker.xml +60 -31
  33. package/android-template/app/src/main/res/values/strings.xml +27 -0
  34. package/android-template/app/src/test/java/com/pakstr/app/LocalServerQueryKeepAliveTest.kt +118 -0
  35. package/android-template/app/src/test/java/com/pakstr/app/crypto/Bip340SignerTest.kt +133 -0
  36. package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip44CryptoTest.kt +199 -0
  37. package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip46ClientKeyProtocolTest.kt +82 -0
  38. package/android-template/app/src/test/java/com/pakstr/app/crypto/Nip46ClientKeyTest.kt +52 -0
  39. package/android-template/app/src/test/java/com/pakstr/app/crypto/NostrEventSerializerTest.kt +42 -0
  40. package/android-template/app/src/test/java/com/pakstr/app/signer/NostrBridgeErrorTest.kt +32 -0
  41. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/BunkerConfigTest.kt +120 -0
  42. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46ModelsTest.kt +111 -0
  43. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingClientTest.kt +271 -0
  44. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingQrCodeTest.kt +54 -0
  45. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46PairingUriTest.kt +80 -0
  46. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46ProtocolTest.kt +578 -0
  47. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/Nip46RelayTransportTest.kt +508 -0
  48. package/android-template/app/src/test/java/com/pakstr/app/signer/bunker/RelayNip46ClientTest.kt +678 -0
  49. package/android-template/gradle/libs.versions.toml +8 -0
  50. 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
- pubkey +
103
- message
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
  }