@account-kit/react-native-signer 4.1.8-alpha → 4.6.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/android/src/main/java/com/accountkit/reactnativesigner/NativeTEKStamperModule.kt +13 -181
  3. package/android/src/main/java/com/accountkit/reactnativesigner/{KeyExtensions.kt → core/KeyExtensions.kt} +1 -1
  4. package/android/src/main/java/com/accountkit/reactnativesigner/{TEKManager.kt → core/TEKManager.kt} +3 -2
  5. package/android/src/main/java/com/accountkit/reactnativesigner/core/TEKStamper.kt +199 -0
  6. package/android/src/main/java/com/accountkit/reactnativesigner/core/errors/NoInjectedBundleException.kt +3 -0
  7. package/android/src/main/java/com/accountkit/reactnativesigner/core/errors/NoTEKException.kt +3 -0
  8. package/android/src/main/java/com/accountkit/reactnativesigner/core/errors/StamperNotInitialized.kt +3 -0
  9. package/package.json +6 -5
  10. package/.github/actions/setup/action.yml +0 -27
  11. package/.github/workflows/ci.yml +0 -157
  12. package/.npmrc +0 -1
  13. package/.turbo/turbo-prepare.log +0 -21
  14. package/.watchmanconfig +0 -1
  15. package/babel.config.js +0 -5
  16. package/example/.bundle/config +0 -2
  17. package/example/.watchmanconfig +0 -1
  18. package/example/Gemfile +0 -9
  19. package/example/README.md +0 -79
  20. package/example/android/app/build.gradle +0 -133
  21. package/example/android/app/debug.keystore +0 -0
  22. package/example/android/app/proguard-rules.pro +0 -10
  23. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  24. package/example/android/app/src/main/AndroidManifest.xml +0 -17
  25. package/example/android/app/src/main/java/accountkit/reactnativesigner/example/MainActivity.kt +0 -27
  26. package/example/android/app/src/main/java/accountkit/reactnativesigner/example/MainApplication.kt +0 -48
  27. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  28. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  29. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  30. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  31. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  32. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  33. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  34. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  35. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  36. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  37. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  38. package/example/android/app/src/main/res/values/strings.xml +0 -3
  39. package/example/android/app/src/main/res/values/styles.xml +0 -9
  40. package/example/android/build.gradle +0 -21
  41. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  42. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  43. package/example/android/gradle.properties +0 -41
  44. package/example/android/gradlew +0 -252
  45. package/example/android/gradlew.bat +0 -94
  46. package/example/android/settings.gradle +0 -6
  47. package/example/app.json +0 -4
  48. package/example/babel.config.js +0 -12
  49. package/example/index.js +0 -6
  50. package/example/ios/.xcode.env +0 -11
  51. package/example/ios/File.swift +0 -6
  52. package/example/ios/Podfile +0 -47
  53. package/example/ios/ReactNativeSignerExample/AppDelegate.h +0 -6
  54. package/example/ios/ReactNativeSignerExample/AppDelegate.mm +0 -31
  55. package/example/ios/ReactNativeSignerExample/Images.xcassets/AppIcon.appiconset/Contents.json +0 -53
  56. package/example/ios/ReactNativeSignerExample/Images.xcassets/Contents.json +0 -6
  57. package/example/ios/ReactNativeSignerExample/Info.plist +0 -52
  58. package/example/ios/ReactNativeSignerExample/LaunchScreen.storyboard +0 -47
  59. package/example/ios/ReactNativeSignerExample/PrivacyInfo.xcprivacy +0 -37
  60. package/example/ios/ReactNativeSignerExample/main.m +0 -10
  61. package/example/ios/ReactNativeSignerExample-Bridging-Header.h +0 -3
  62. package/example/ios/ReactNativeSignerExample.xcodeproj/project.pbxproj +0 -690
  63. package/example/ios/ReactNativeSignerExample.xcodeproj/xcshareddata/xcschemes/ReactNativeSignerExample.xcscheme +0 -98
  64. package/example/ios/ReactNativeSignerExampleTests/Info.plist +0 -24
  65. package/example/ios/ReactNativeSignerExampleTests/ReactNativeSignerExampleTests.m +0 -66
  66. package/example/jest.config.js +0 -3
  67. package/example/metro.config.js +0 -22
  68. package/example/package.json +0 -56
  69. package/example/react-native.config.js +0 -15
  70. package/example/redirect-server/index.ts +0 -19
  71. package/example/src/App.tsx +0 -30
  72. package/example/src/screens/Home.tsx +0 -149
  73. package/example/turbo.json +0 -38
  74. package/react-native.config.js +0 -11
  75. package/tsconfig.build.json +0 -4
  76. package/tsconfig.json +0 -30
  77. package/turbo.json +0 -9
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Alchemy Insights, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,97 +1,16 @@
1
1
  package com.accountkit.reactnativesigner
2
2
 
3
- import androidx.security.crypto.EncryptedSharedPreferences
4
- import androidx.security.crypto.MasterKey
3
+ import com.accountkit.reactnativesigner.core.TEKStamper
5
4
  import com.facebook.react.bridge.Arguments
6
5
  import com.facebook.react.bridge.Promise
7
6
  import com.facebook.react.bridge.ReactApplicationContext
8
7
  import com.facebook.react.module.annotations.ReactModule
9
- import com.google.crypto.tink.config.TinkConfig
10
- import com.google.crypto.tink.subtle.Base64
11
- import com.google.crypto.tink.subtle.EllipticCurves
12
- import java.nio.ByteBuffer
13
- import java.security.KeyFactory
14
- import java.security.Security
15
- import java.security.Signature
16
- import kotlinx.serialization.Serializable
17
- import kotlinx.serialization.encodeToString
18
- import kotlinx.serialization.json.Json
19
- import org.bitcoinj.core.Base58
20
- import org.bouncycastle.jce.ECNamedCurveTable
21
- import org.bouncycastle.jce.provider.BouncyCastleProvider
22
- import org.bouncycastle.jce.spec.ECPublicKeySpec
23
-
24
- @Serializable
25
- data class ApiStamp(val publicKey: String, val scheme: String, val signature: String)
26
-
27
- private const val BUNDLE_PRIVATE_KEY = "BUNDLE_PRIVATE_KEY"
28
- private const val BUNDLE_PUBLIC_KEY = "BUNDLE_PUBLIC_KEY"
29
8
 
30
9
  @ReactModule(name = NativeTEKStamperModule.NAME)
31
10
  class NativeTEKStamperModule(reactContext: ReactApplicationContext) :
32
11
  NativeTEKStamperSpec(reactContext) {
33
12
 
34
- private val context = reactContext
35
-
36
- // This is how the docs for EncryptedSharedPreferences recommend creating this setup
37
- // NOTE: we can further customize the permissions around accessing this master key and the keys
38
- // used to generate it by using the .setKeyGenParameterSpec() method on this builder
39
- // this would allow us to further specify the access requirements to this key
40
- //
41
- // we should explore the best practices on how to do this once we reach a phase of further
42
- // cleanup
43
- private val masterKey =
44
- MasterKey.Builder(context.applicationContext)
45
- .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
46
- // requires that the phone be unlocked
47
- .setUserAuthenticationRequired(false)
48
- .build()
49
-
50
- /**
51
- * We are using EncryptedSharedPreferences to store 2 pieces of data
52
- * 1. the TEK keypair - this is the ephemeral key-pair that Turnkey will use to encrypt the
53
- * bundle with
54
- * 2. the decrypted private key for a session
55
- *
56
- * The reason we are not using the android key store for either of these things is because
57
- * 1. For us to be able to import the private key in the bundle into the KeyStore, Turnkey has
58
- * to return the key in a different format (AFAIK):
59
- * https://developer.android.com/privacy-and-security/keystore#ImportingEncryptedKeys
60
- * 2. If we store the TEK in the KeyStore, then we have to roll our own HPKE decrypt function as
61
- * there's no off the shelf solution (that I could find) to do the HPKE decryption. Rolling our
62
- * own decryption feels wrong given we are not experts on this and don't have a good way to
63
- * verify our implementation (and I don't trust the ChatGPT output to be correct. Even if it is,
64
- * there's no guarantee we can test all the edge cases since those are unknown unknowns)
65
- *
66
- * NOTE: this isn't too far off from how Turnkey recommends doing it in Swift
67
- * https://github.com/tkhq/swift-sdk/blob/5817374a7cbd4c99b7ea90b170363dc2bf6c59b9/docs/email-auth.md#email-authentication
68
- *
69
- * The open question is if the storage of the decrypted private key is secure enough though
70
- */
71
- private val sharedPreferences =
72
- EncryptedSharedPreferences.create(
73
- context,
74
- "tek_stamper_shared_prefs",
75
- masterKey,
76
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
77
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
78
- )
79
-
80
- private val tekManager = HpkeTEKManager(sharedPreferences)
81
-
82
- init {
83
- TinkConfig.register()
84
-
85
- if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).javaClass !=
86
- BouncyCastleProvider::class.java
87
- ) {
88
- Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
89
- }
90
-
91
- if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
92
- Security.addProvider(BouncyCastleProvider())
93
- }
94
- }
13
+ private val stamper = TEKStamper(reactContext.applicationContext)
95
14
 
96
15
  override fun getName(): String {
97
16
  return NAME
@@ -99,65 +18,25 @@ class NativeTEKStamperModule(reactContext: ReactApplicationContext) :
99
18
 
100
19
  override fun init(promise: Promise) {
101
20
  try {
102
- val tekPublicKey = tekManager.createTEK()
21
+ val tekPublicKey = stamper.init()
103
22
 
104
- return promise.resolve(tekPublicKey.toHex())
23
+ return promise.resolve(tekPublicKey)
105
24
  } catch (e: Exception) {
106
25
  promise.reject(e)
107
26
  }
108
27
  }
109
28
 
110
29
  override fun clear() {
111
- sharedPreferences.edit().clear().apply()
30
+ stamper.clear()
112
31
  }
113
32
 
114
33
  override fun publicKey(): String? {
115
- return tekManager.publicKeyHex()
34
+ return stamper.publicKey()
116
35
  }
117
36
 
118
37
  override fun injectCredentialBundle(bundle: String, promise: Promise) {
119
38
  try {
120
- val tekPublicKey =
121
- tekManager.publicKey()
122
- ?: return promise.reject(Exception("Stamper has not been initialized"))
123
-
124
- val decodedBundle = Base58.decodeChecked(bundle)
125
- val buffer = ByteBuffer.wrap(decodedBundle)
126
- val ephemeralPublicKeyLength = 33
127
- val ephemeralPublicKeyBytes = ByteArray(ephemeralPublicKeyLength)
128
- buffer.get(ephemeralPublicKeyBytes)
129
- val ephemeralPublicKey =
130
- EllipticCurves.getEcPublicKey(
131
- EllipticCurves.CurveType.NIST_P256,
132
- EllipticCurves.PointFormatType.COMPRESSED,
133
- ephemeralPublicKeyBytes,
134
- )
135
- .toBytes(EllipticCurves.PointFormatType.UNCOMPRESSED)
136
-
137
- val ciphertext = ByteArray(buffer.remaining())
138
- buffer.get(ciphertext)
139
-
140
- val aad = ephemeralPublicKey + tekPublicKey.toByteArray()
141
-
142
- val decryptedKey =
143
- tekManager.hpkeDecrypt(
144
- ephemeralPublicKey,
145
- ciphertext,
146
- "turnkey_hpke".toByteArray(),
147
- aad
148
- )
149
-
150
- val (publicKeyBytes, privateKeyBytes) = privateKeyToKeyPair(decryptedKey)
151
-
152
- sharedPreferences
153
- .edit()
154
- .putString(BUNDLE_PRIVATE_KEY, privateKeyBytes.toHex().lowercase())
155
- .apply()
156
-
157
- sharedPreferences
158
- .edit()
159
- .putString(BUNDLE_PUBLIC_KEY, publicKeyBytes.toHex().lowercase())
160
- .apply()
39
+ stamper.injectCredentialBundle(bundle)
161
40
 
162
41
  return promise.resolve(true)
163
42
  } catch (e: Exception) {
@@ -167,67 +46,20 @@ class NativeTEKStamperModule(reactContext: ReactApplicationContext) :
167
46
 
168
47
  override fun stamp(payload: String, promise: Promise) {
169
48
  try {
170
- val signingKeyHex =
171
- sharedPreferences.getString(BUNDLE_PRIVATE_KEY, null)
172
- ?: return promise.reject(
173
- Exception("No injected bundle, did you complete auth?")
174
- )
175
-
176
- val publicSigningKeyHex =
177
- sharedPreferences.getString(BUNDLE_PUBLIC_KEY, null)
178
- ?: return promise.reject(
179
- Exception("No injected bundle, did you complete auth?")
180
- )
181
-
182
- val ecPrivateKey =
183
- EllipticCurves.getEcPrivateKey(
184
- EllipticCurves.CurveType.NIST_P256,
185
- signingKeyHex.fromHex()
186
- )
187
-
188
- val signer = Signature.getInstance("SHA256withECDSA")
189
- signer.initSign(ecPrivateKey)
190
- signer.update(payload.toByteArray())
191
- val signature = signer.sign()
192
-
193
- val apiStamp =
194
- ApiStamp(publicSigningKeyHex, "SIGNATURE_SCHEME_TK_API_P256", signature.toHex())
49
+ val stamp = stamper.stamp(payload)
195
50
 
196
- val stamp = Arguments.createMap()
197
- stamp.putString("stampHeaderName", "X-Stamp")
198
- stamp.putString(
51
+ val response = Arguments.createMap()
52
+ response.putString("stampHeaderName", stamp.stampHeaderName)
53
+ response.putString(
199
54
  "stampHeaderValue",
200
- Base64.urlSafeEncode(Json.encodeToString(apiStamp).toByteArray())
55
+ stamp.stampHeaderValue
201
56
  )
202
- return promise.resolve(stamp)
57
+ return promise.resolve(response)
203
58
  } catch (e: Exception) {
204
59
  promise.reject(e)
205
60
  }
206
61
  }
207
62
 
208
- private fun privateKeyToKeyPair(privateKey: ByteArray): Pair<ByteArray, ByteArray> {
209
- val ecPrivateKey =
210
- EllipticCurves.getEcPrivateKey(EllipticCurves.CurveType.NIST_P256, privateKey)
211
-
212
- // compute the public key
213
- val s = ecPrivateKey.s
214
- val bcSpec = ECNamedCurveTable.getParameterSpec("secp256r1")
215
- val pubSpec = ECPublicKeySpec(bcSpec.g.multiply(s).normalize(), bcSpec)
216
- val keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME)
217
-
218
- val ecPublicKey = EllipticCurves.getEcPublicKey(keyFactory.generatePublic(pubSpec).encoded)
219
-
220
- // verify the key pair
221
- EllipticCurves.validatePublicKey(ecPublicKey, ecPrivateKey)
222
-
223
- // compress it to match turnkey expectations
224
- val compressedPublicKey =
225
- ecPublicKey.toBytes(
226
- EllipticCurves.PointFormatType.COMPRESSED,
227
- )
228
- return Pair(compressedPublicKey, privateKey)
229
- }
230
-
231
63
  companion object {
232
64
  const val NAME = "NativeTEKStamper"
233
65
  }
@@ -1,4 +1,4 @@
1
- package com.accountkit.reactnativesigner
1
+ package com.accountkit.reactnativesigner.core
2
2
 
3
3
  import com.google.crypto.tink.CleartextKeysetHandle
4
4
  import com.google.crypto.tink.InsecureSecretKeyAccess
@@ -1,6 +1,7 @@
1
- package com.accountkit.reactnativesigner
1
+ package com.accountkit.reactnativesigner.core
2
2
 
3
3
  import android.content.SharedPreferences
4
+ import com.accountkit.reactnativesigner.core.errors.NoTEKException
4
5
  import com.google.crypto.tink.InsecureSecretKeyAccess
5
6
  import com.google.crypto.tink.KeyTemplate
6
7
  import com.google.crypto.tink.KeysetHandle
@@ -31,7 +32,7 @@ class HpkeTEKManager(private val sharedPreferences: SharedPreferences) {
31
32
  // val decryptedKey = hybridDecrypt.decrypt(ciphertext, "turnkey_hpke".toByteArray())
32
33
  // the hybridDecrypt.decrypt that google exposes doesn't allow us to pass in
33
34
  // the aad that's needed to complete decryption
34
- val keyHandle = getKeysetHandle() ?: throw IllegalStateException("No TEK found!")
35
+ val keyHandle = getKeysetHandle() ?: throw NoTEKException()
35
36
 
36
37
  val recipient = HpkeContext.createRecipientContext(
37
38
  encapsulatePublicKey,
@@ -0,0 +1,199 @@
1
+ package com.accountkit.reactnativesigner.core
2
+
3
+ import android.content.Context
4
+ import androidx.security.crypto.EncryptedSharedPreferences
5
+ import androidx.security.crypto.MasterKey
6
+ import com.accountkit.reactnativesigner.core.errors.NoInjectedBundleException
7
+ import com.accountkit.reactnativesigner.core.errors.StamperNotInitializedException
8
+ import com.google.crypto.tink.config.TinkConfig
9
+ import com.google.crypto.tink.subtle.Base64
10
+ import com.google.crypto.tink.subtle.EllipticCurves
11
+ import kotlinx.serialization.Serializable
12
+ import kotlinx.serialization.encodeToString
13
+ import kotlinx.serialization.json.Json
14
+ import org.bitcoinj.core.Base58
15
+ import org.bouncycastle.jce.ECNamedCurveTable
16
+ import org.bouncycastle.jce.provider.BouncyCastleProvider
17
+ import org.bouncycastle.jce.spec.ECPublicKeySpec
18
+ import java.nio.ByteBuffer
19
+ import java.security.KeyFactory
20
+ import java.security.Security
21
+ import java.security.Signature
22
+
23
+ @Serializable
24
+ data class ApiStamp(val publicKey: String, val scheme: String, val signature: String)
25
+
26
+ data class Stamp(val stampHeaderName: String, val stampHeaderValue: String)
27
+
28
+ private const val BUNDLE_PRIVATE_KEY = "BUNDLE_PRIVATE_KEY"
29
+ private const val BUNDLE_PUBLIC_KEY = "BUNDLE_PUBLIC_KEY"
30
+
31
+ class TEKStamper(context: Context) {
32
+ // This is how the docs for EncryptedSharedPreferences recommend creating this setup
33
+ // NOTE: we can further customize the permissions around accessing this master key and the keys
34
+ // used to generate it by using the .setKeyGenParameterSpec() method on this builder
35
+ // this would allow us to further specify the access requirements to this key
36
+ //
37
+ // we should explore the best practices on how to do this once we reach a phase of further
38
+ // cleanup
39
+ private val masterKey =
40
+ MasterKey.Builder(context.applicationContext)
41
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
42
+ // requires that the phone be unlocked
43
+ .setUserAuthenticationRequired(false)
44
+ .build()
45
+
46
+ /**
47
+ * We are using EncryptedSharedPreferences to store 2 pieces of data
48
+ * 1. the TEK keypair - this is the ephemeral key-pair that Turnkey will use to encrypt the
49
+ * bundle with
50
+ * 2. the decrypted private key for a session
51
+ *
52
+ * The reason we are not using the android key store for either of these things is because
53
+ * 1. For us to be able to import the private key in the bundle into the KeyStore, Turnkey has
54
+ * to return the key in a different format (AFAIK):
55
+ * https://developer.android.com/privacy-and-security/keystore#ImportingEncryptedKeys
56
+ * 2. If we store the TEK in the KeyStore, then we have to roll our own HPKE decrypt function as
57
+ * there's no off the shelf solution (that I could find) to do the HPKE decryption. Rolling our
58
+ * own decryption feels wrong given we are not experts on this and don't have a good way to
59
+ * verify our implementation (and I don't trust the ChatGPT output to be correct. Even if it is,
60
+ * there's no guarantee we can test all the edge cases since those are unknown unknowns)
61
+ *
62
+ * NOTE: this isn't too far off from how Turnkey recommends doing it in Swift
63
+ * https://github.com/tkhq/swift-sdk/blob/5817374a7cbd4c99b7ea90b170363dc2bf6c59b9/docs/email-auth.md#email-authentication
64
+ *
65
+ * The open question is if the storage of the decrypted private key is secure enough though
66
+ */
67
+ private val sharedPreferences =
68
+ EncryptedSharedPreferences.create(
69
+ context,
70
+ "tek_stamper_shared_prefs",
71
+ masterKey,
72
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
73
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
74
+ )
75
+
76
+ private val tekManager = HpkeTEKManager(sharedPreferences)
77
+
78
+ init {
79
+ TinkConfig.register()
80
+
81
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).javaClass !=
82
+ BouncyCastleProvider::class.java
83
+ ) {
84
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
85
+ }
86
+
87
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
88
+ Security.addProvider(BouncyCastleProvider())
89
+ }
90
+ }
91
+
92
+ fun init(): String {
93
+ return tekManager.createTEK().toHex()
94
+ }
95
+
96
+ fun clear() {
97
+ sharedPreferences.edit().clear().apply()
98
+ }
99
+
100
+ fun publicKey(): String? {
101
+ return tekManager.publicKeyHex()
102
+ }
103
+
104
+ fun injectCredentialBundle(bundle: String) {
105
+ val tekPublicKey =
106
+ tekManager.publicKey()
107
+ ?: throw StamperNotInitializedException()
108
+
109
+ val decodedBundle = Base58.decodeChecked(bundle)
110
+ val buffer = ByteBuffer.wrap(decodedBundle)
111
+ val ephemeralPublicKeyLength = 33
112
+ val ephemeralPublicKeyBytes = ByteArray(ephemeralPublicKeyLength)
113
+ buffer.get(ephemeralPublicKeyBytes)
114
+ val ephemeralPublicKey =
115
+ EllipticCurves.getEcPublicKey(
116
+ EllipticCurves.CurveType.NIST_P256,
117
+ EllipticCurves.PointFormatType.COMPRESSED,
118
+ ephemeralPublicKeyBytes,
119
+ )
120
+ .toBytes(EllipticCurves.PointFormatType.UNCOMPRESSED)
121
+
122
+ val ciphertext = ByteArray(buffer.remaining())
123
+ buffer.get(ciphertext)
124
+
125
+ val aad = ephemeralPublicKey + tekPublicKey.toByteArray()
126
+
127
+ val decryptedKey =
128
+ tekManager.hpkeDecrypt(
129
+ ephemeralPublicKey,
130
+ ciphertext,
131
+ "turnkey_hpke".toByteArray(),
132
+ aad
133
+ )
134
+
135
+ val (publicKeyBytes, privateKeyBytes) = privateKeyToKeyPair(decryptedKey)
136
+
137
+ sharedPreferences
138
+ .edit()
139
+ .putString(BUNDLE_PRIVATE_KEY, privateKeyBytes.toHex().lowercase())
140
+ .apply()
141
+
142
+ sharedPreferences
143
+ .edit()
144
+ .putString(BUNDLE_PUBLIC_KEY, publicKeyBytes.toHex().lowercase())
145
+ .apply()
146
+ }
147
+
148
+ fun stamp(payload: String): Stamp {
149
+ val signingKeyHex =
150
+ sharedPreferences.getString(BUNDLE_PRIVATE_KEY, null)
151
+ ?: throw NoInjectedBundleException()
152
+
153
+ val publicSigningKeyHex =
154
+ sharedPreferences.getString(BUNDLE_PUBLIC_KEY, null)
155
+ ?: throw NoInjectedBundleException()
156
+
157
+ val ecPrivateKey =
158
+ EllipticCurves.getEcPrivateKey(
159
+ EllipticCurves.CurveType.NIST_P256,
160
+ signingKeyHex.fromHex()
161
+ )
162
+
163
+ val signer = Signature.getInstance("SHA256withECDSA")
164
+ signer.initSign(ecPrivateKey)
165
+ signer.update(payload.toByteArray())
166
+ val signature = signer.sign()
167
+
168
+ val apiStamp =
169
+ ApiStamp(publicSigningKeyHex, "SIGNATURE_SCHEME_TK_API_P256", signature.toHex())
170
+
171
+ return Stamp(
172
+ "X-Stamp",
173
+ Base64.urlSafeEncode(Json.encodeToString(apiStamp).toByteArray())
174
+ )
175
+ }
176
+
177
+ private fun privateKeyToKeyPair(privateKey: ByteArray): Pair<ByteArray, ByteArray> {
178
+ val ecPrivateKey =
179
+ EllipticCurves.getEcPrivateKey(EllipticCurves.CurveType.NIST_P256, privateKey)
180
+
181
+ // compute the public key
182
+ val s = ecPrivateKey.s
183
+ val bcSpec = ECNamedCurveTable.getParameterSpec("secp256r1")
184
+ val pubSpec = ECPublicKeySpec(bcSpec.g.multiply(s).normalize(), bcSpec)
185
+ val keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME)
186
+
187
+ val ecPublicKey = EllipticCurves.getEcPublicKey(keyFactory.generatePublic(pubSpec).encoded)
188
+
189
+ // verify the key pair
190
+ EllipticCurves.validatePublicKey(ecPublicKey, ecPrivateKey)
191
+
192
+ // compress it to match turnkey expectations
193
+ val compressedPublicKey =
194
+ ecPublicKey.toBytes(
195
+ EllipticCurves.PointFormatType.COMPRESSED,
196
+ )
197
+ return Pair(compressedPublicKey, privateKey)
198
+ }
199
+ }
@@ -0,0 +1,3 @@
1
+ package com.accountkit.reactnativesigner.core.errors
2
+
3
+ class NoInjectedBundleException: IllegalStateException("No injected bundle, did you complete auth?")
@@ -0,0 +1,3 @@
1
+ package com.accountkit.reactnativesigner.core.errors
2
+
3
+ class NoTEKException: IllegalStateException("No TEK found!")
@@ -0,0 +1,3 @@
1
+ package com.accountkit.reactnativesigner.core.errors
2
+
3
+ class StamperNotInitializedException: IllegalStateException("Stamper has not been initialized")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@account-kit/react-native-signer",
3
- "version": "4.1.8-alpha",
3
+ "version": "4.6.1",
4
4
  "description": "React Native compatible Account Kit signer",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",
@@ -39,7 +39,7 @@
39
39
  "scripts": {
40
40
  "test": "jest",
41
41
  "typecheck": "tsc",
42
- "build:rn": "bob build && yarn typecheck && yarn turbo run build:android",
42
+ "build": "yarn typecheck",
43
43
  "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
44
44
  "prepare": "bob build"
45
45
  },
@@ -146,8 +146,9 @@
146
146
  "version": "0.42.2"
147
147
  },
148
148
  "dependencies": {
149
- "@aa-sdk/core": "^4.3.0",
150
- "@account-kit/signer": "^4.3.0",
149
+ "@aa-sdk/core": "^4.6.1",
150
+ "@account-kit/signer": "^4.6.1",
151
151
  "viem": "^2.21.40"
152
- }
152
+ },
153
+ "gitHead": "2a88216fe9074fe72e08ddf0a1bca68c19388a8e"
153
154
  }
@@ -1,27 +0,0 @@
1
- name: Setup
2
- description: Setup Node.js and install dependencies
3
-
4
- runs:
5
- using: composite
6
- steps:
7
- - name: Setup Node.js
8
- uses: actions/setup-node@v3
9
- with:
10
- node-version-file: .nvmrc
11
-
12
- - name: Cache dependencies
13
- id: yarn-cache
14
- uses: actions/cache@v3
15
- with:
16
- path: |
17
- **/node_modules
18
- .yarn/install-state.gz
19
- key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }}
20
- restore-keys: |
21
- ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
22
- ${{ runner.os }}-yarn-
23
-
24
- - name: Install dependencies
25
- if: steps.yarn-cache.outputs.cache-hit != 'true'
26
- run: yarn install --immutable
27
- shell: bash