@authon/react-native-wallet 0.0.0-stage → 0.4.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.
Files changed (183) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +251 -2
  3. package/android/build.gradle +40 -0
  4. package/android/consumer-rules.pro +10 -0
  5. package/android/src/main/AndroidManifest.xml +8 -0
  6. package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +235 -0
  7. package/android/src/vendor/GENERATED.md +4 -0
  8. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AndroidCapabilityProbe.kt +86 -0
  9. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +218 -0
  10. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bitcoin.kt +296 -0
  11. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
  12. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Chains.kt +157 -0
  13. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
  14. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Der.kt +17 -0
  15. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DeviceStore.kt +170 -0
  16. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt +141 -0
  17. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Eip712.kt +176 -0
  18. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Encodings.kt +133 -0
  19. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmAccount.kt +45 -0
  20. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt +180 -0
  21. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/GfTables.kt +44 -0
  22. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/HdWallet.kt +130 -0
  23. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +86 -0
  24. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/JsValues.kt +83 -0
  25. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +236 -0
  26. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Prf.kt +111 -0
  27. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Proof.kt +134 -0
  28. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Shamir.kt +106 -0
  29. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/SignSummary.kt +335 -0
  30. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Solana.kt +195 -0
  31. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Tron.kt +184 -0
  32. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +361 -0
  33. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletCapabilities.kt +91 -0
  34. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +74 -0
  35. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +551 -0
  36. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +78 -0
  37. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Xrp.kt +193 -0
  38. package/build/authonContext.d.ts +19 -0
  39. package/build/authonContext.d.ts.map +1 -0
  40. package/build/authonContext.js +20 -0
  41. package/build/authonContext.js.map +1 -0
  42. package/build/client.d.ts +92 -0
  43. package/build/client.d.ts.map +1 -0
  44. package/build/client.js +431 -0
  45. package/build/client.js.map +1 -0
  46. package/build/errors.d.ts +25 -0
  47. package/build/errors.d.ts.map +1 -0
  48. package/build/errors.js +15 -0
  49. package/build/errors.js.map +1 -0
  50. package/build/index.d.ts +7 -0
  51. package/build/index.d.ts.map +1 -0
  52. package/build/index.js +4 -0
  53. package/build/index.js.map +1 -0
  54. package/build/native.d.ts +43 -0
  55. package/build/native.d.ts.map +1 -0
  56. package/build/native.js +12 -0
  57. package/build/native.js.map +1 -0
  58. package/build/types.d.ts +167 -0
  59. package/build/types.d.ts.map +1 -0
  60. package/build/types.js +3 -0
  61. package/build/types.js.map +1 -0
  62. package/build/useAuthonWallet.d.ts +25 -0
  63. package/build/useAuthonWallet.d.ts.map +1 -0
  64. package/build/useAuthonWallet.js +77 -0
  65. package/build/useAuthonWallet.js.map +1 -0
  66. package/expo-module.config.json +9 -0
  67. package/ios/AuthonReactNativeWallet.podspec +41 -0
  68. package/ios/AuthonWalletModule.swift +233 -0
  69. package/ios/vendor/AuthonWallet/AuthonWallet.swift +232 -0
  70. package/ios/vendor/AuthonWallet/Capabilities.swift +149 -0
  71. package/ios/vendor/AuthonWallet/Chains/Bitcoin.swift +296 -0
  72. package/ios/vendor/AuthonWallet/Chains/Solana.swift +207 -0
  73. package/ios/vendor/AuthonWallet/Chains/Tron.swift +181 -0
  74. package/ios/vendor/AuthonWallet/Chains/XRP.swift +191 -0
  75. package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
  76. package/ios/vendor/AuthonWallet/Crypto/Chains.swift +278 -0
  77. package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
  78. package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +128 -0
  79. package/ios/vendor/AuthonWallet/Crypto/Shamir.swift +123 -0
  80. package/ios/vendor/AuthonWallet/EVM/Address.swift +57 -0
  81. package/ios/vendor/AuthonWallet/EVM/EvmAccount.swift +48 -0
  82. package/ios/vendor/AuthonWallet/EVM/Transaction.swift +165 -0
  83. package/ios/vendor/AuthonWallet/EVM/TypedData.swift +193 -0
  84. package/ios/vendor/AuthonWallet/Support/BigInteger.swift +220 -0
  85. package/ios/vendor/AuthonWallet/Support/Bytes.swift +89 -0
  86. package/ios/vendor/AuthonWallet/Support/JSONValue.swift +341 -0
  87. package/ios/vendor/AuthonWallet/UI/WalletApprover.swift +250 -0
  88. package/ios/vendor/AuthonWallet/Wallet/Backup.swift +69 -0
  89. package/ios/vendor/AuthonWallet/Wallet/DeviceStore.swift +351 -0
  90. package/ios/vendor/AuthonWallet/Wallet/Passkeys.swift +190 -0
  91. package/ios/vendor/AuthonWallet/Wallet/Proof.swift +143 -0
  92. package/ios/vendor/AuthonWallet/Wallet/Shares.swift +116 -0
  93. package/ios/vendor/AuthonWallet/Wallet/Summary.swift +342 -0
  94. package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +361 -0
  95. package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +654 -0
  96. package/ios/vendor/AuthonWallet/WalletError.swift +84 -0
  97. package/ios/vendor/CKeccak/LICENSE +22 -0
  98. package/ios/vendor/CKeccak/README.md +30 -0
  99. package/ios/vendor/CKeccak/byte_order.h +59 -0
  100. package/ios/vendor/CKeccak/include/ckeccak.h +13 -0
  101. package/ios/vendor/CKeccak/memzero.c +75 -0
  102. package/ios/vendor/CKeccak/memzero.h +8 -0
  103. package/ios/vendor/CKeccak/options.h +99 -0
  104. package/ios/vendor/CKeccak/ripemd160.c +343 -0
  105. package/ios/vendor/CKeccak/ripemd160.h +22 -0
  106. package/ios/vendor/CKeccak/sha3.c +413 -0
  107. package/ios/vendor/CKeccak/sha3.h +89 -0
  108. package/ios/vendor/CMonocypher/LICENCE.md +173 -0
  109. package/ios/vendor/CMonocypher/README.md +27 -0
  110. package/ios/vendor/CMonocypher/include/monocypher-ed25519.h +140 -0
  111. package/ios/vendor/CMonocypher/include/monocypher.h +321 -0
  112. package/ios/vendor/CMonocypher/monocypher-ed25519.c +500 -0
  113. package/ios/vendor/CMonocypher/monocypher.c +2956 -0
  114. package/ios/vendor/CSecp256k1/COPYING +19 -0
  115. package/ios/vendor/CSecp256k1/README.md +35 -0
  116. package/ios/vendor/CSecp256k1/build/precomputed_ecmult.c +3 -0
  117. package/ios/vendor/CSecp256k1/build/precomputed_ecmult_gen.c +3 -0
  118. package/ios/vendor/CSecp256k1/build/secp256k1.c +3 -0
  119. package/ios/vendor/CSecp256k1/include/secp256k1.h +899 -0
  120. package/ios/vendor/CSecp256k1/include/secp256k1_preallocated.h +134 -0
  121. package/ios/vendor/CSecp256k1/include/secp256k1_recovery.h +113 -0
  122. package/ios/vendor/CSecp256k1/src/assumptions.h +87 -0
  123. package/ios/vendor/CSecp256k1/src/checkmem.h +102 -0
  124. package/ios/vendor/CSecp256k1/src/ecdsa.h +21 -0
  125. package/ios/vendor/CSecp256k1/src/ecdsa_impl.h +304 -0
  126. package/ios/vendor/CSecp256k1/src/eckey.h +25 -0
  127. package/ios/vendor/CSecp256k1/src/eckey_impl.h +92 -0
  128. package/ios/vendor/CSecp256k1/src/ecmult.h +61 -0
  129. package/ios/vendor/CSecp256k1/src/ecmult_const.h +38 -0
  130. package/ios/vendor/CSecp256k1/src/ecmult_const_impl.h +399 -0
  131. package/ios/vendor/CSecp256k1/src/ecmult_gen.h +143 -0
  132. package/ios/vendor/CSecp256k1/src/ecmult_gen_impl.h +341 -0
  133. package/ios/vendor/CSecp256k1/src/ecmult_impl.h +853 -0
  134. package/ios/vendor/CSecp256k1/src/field.h +350 -0
  135. package/ios/vendor/CSecp256k1/src/field_10x26.h +57 -0
  136. package/ios/vendor/CSecp256k1/src/field_10x26_impl.h +1232 -0
  137. package/ios/vendor/CSecp256k1/src/field_5x52.h +62 -0
  138. package/ios/vendor/CSecp256k1/src/field_5x52_impl.h +522 -0
  139. package/ios/vendor/CSecp256k1/src/field_5x52_int128_impl.h +274 -0
  140. package/ios/vendor/CSecp256k1/src/field_impl.h +457 -0
  141. package/ios/vendor/CSecp256k1/src/group.h +212 -0
  142. package/ios/vendor/CSecp256k1/src/group_impl.h +974 -0
  143. package/ios/vendor/CSecp256k1/src/hash.h +44 -0
  144. package/ios/vendor/CSecp256k1/src/hash_impl.h +299 -0
  145. package/ios/vendor/CSecp256k1/src/hsort.h +33 -0
  146. package/ios/vendor/CSecp256k1/src/hsort_impl.h +125 -0
  147. package/ios/vendor/CSecp256k1/src/int128.h +90 -0
  148. package/ios/vendor/CSecp256k1/src/int128_impl.h +18 -0
  149. package/ios/vendor/CSecp256k1/src/int128_native.h +19 -0
  150. package/ios/vendor/CSecp256k1/src/int128_native_impl.h +94 -0
  151. package/ios/vendor/CSecp256k1/src/int128_struct.h +14 -0
  152. package/ios/vendor/CSecp256k1/src/int128_struct_impl.h +205 -0
  153. package/ios/vendor/CSecp256k1/src/modinv32.h +43 -0
  154. package/ios/vendor/CSecp256k1/src/modinv32_impl.h +725 -0
  155. package/ios/vendor/CSecp256k1/src/modinv64.h +47 -0
  156. package/ios/vendor/CSecp256k1/src/modinv64_impl.h +780 -0
  157. package/ios/vendor/CSecp256k1/src/modules/recovery/main_impl.h +159 -0
  158. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.c +16456 -0
  159. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.h +38 -0
  160. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.c +1779 -0
  161. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.h +26 -0
  162. package/ios/vendor/CSecp256k1/src/scalar.h +105 -0
  163. package/ios/vendor/CSecp256k1/src/scalar_4x64.h +19 -0
  164. package/ios/vendor/CSecp256k1/src/scalar_4x64_impl.h +1000 -0
  165. package/ios/vendor/CSecp256k1/src/scalar_8x32.h +19 -0
  166. package/ios/vendor/CSecp256k1/src/scalar_8x32_impl.h +816 -0
  167. package/ios/vendor/CSecp256k1/src/scalar_impl.h +321 -0
  168. package/ios/vendor/CSecp256k1/src/scalar_low.h +24 -0
  169. package/ios/vendor/CSecp256k1/src/scalar_low_impl.h +206 -0
  170. package/ios/vendor/CSecp256k1/src/scratch.h +44 -0
  171. package/ios/vendor/CSecp256k1/src/scratch_impl.h +99 -0
  172. package/ios/vendor/CSecp256k1/src/secp256k1.c +831 -0
  173. package/ios/vendor/CSecp256k1/src/selftest.h +32 -0
  174. package/ios/vendor/CSecp256k1/src/util.h +451 -0
  175. package/ios/vendor/GENERATED.md +4 -0
  176. package/package.json +66 -3
  177. package/src/authonContext.ts +30 -0
  178. package/src/client.ts +459 -0
  179. package/src/errors.ts +58 -0
  180. package/src/index.ts +26 -0
  181. package/src/native.ts +52 -0
  182. package/src/types.ts +171 -0
  183. package/src/useAuthonWallet.ts +103 -0
@@ -0,0 +1,157 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Chains.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import org.bouncycastle.crypto.digests.RIPEMD160Digest
5
+ import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
6
+ import org.web3j.crypto.Bip32ECKeyPair
7
+ import org.web3j.crypto.Hash
8
+ import org.web3j.crypto.Keys
9
+ import org.web3j.crypto.Sign
10
+ import javax.crypto.Mac
11
+ import javax.crypto.spec.SecretKeySpec
12
+
13
+ /** Chains a wallet derives addresses for (NATIVE.md section 11). [wire] is the contract string. */
14
+ enum class WalletChain(val wire: String) {
15
+ EVM("evm"),
16
+ TRON("tron"),
17
+ XRP("xrp"),
18
+ BITCOIN("bitcoin"),
19
+ BITCOIN_TESTNET("bitcoin-testnet"),
20
+ SOLANA("solana");
21
+
22
+ companion object {
23
+ fun fromWire(value: String): WalletChain = entries.firstOrNull { it.wire == value }
24
+ ?: throw walletError(WalletErrorCode.INVALID_REQUEST, "chain must be one of ${entries.joinToString(", ") { it.wire }}")
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Per-chain account addresses from the BIP39 seed, with the paths of standard wallets
30
+ * (`apps/wallet/src/chains.ts`). Curve math comes from web3j / Bouncy Castle; only the address
31
+ * encodings and SLIP-0010 chaining are assembled here. Address derivation only, no signing.
32
+ */
33
+ internal object Chains {
34
+ private const val H = Bip32ECKeyPair.HARDENED_BIT
35
+
36
+ fun path(chain: WalletChain, index: Long): String = when (chain) {
37
+ WalletChain.EVM -> "m/44'/60'/0'/0/$index"
38
+ WalletChain.TRON -> "m/44'/195'/$index'/0/0"
39
+ WalletChain.XRP -> "m/44'/144'/$index'/0/0"
40
+ WalletChain.BITCOIN -> "m/84'/0'/$index'/0/0"
41
+ WalletChain.BITCOIN_TESTNET -> "m/84'/1'/$index'/0/0"
42
+ WalletChain.SOLANA -> "m/44'/501'/$index'/0'"
43
+ }
44
+
45
+ private fun secpPath(chain: WalletChain, index: Int): IntArray = when (chain) {
46
+ WalletChain.EVM -> intArrayOf(44 or H, 60 or H, 0 or H, 0, index)
47
+ WalletChain.TRON -> intArrayOf(44 or H, 195 or H, index or H, 0, 0)
48
+ WalletChain.XRP -> intArrayOf(44 or H, 144 or H, index or H, 0, 0)
49
+ WalletChain.BITCOIN -> intArrayOf(84 or H, 0 or H, index or H, 0, 0)
50
+ WalletChain.BITCOIN_TESTNET -> intArrayOf(84 or H, 1 or H, index or H, 0, 0)
51
+ WalletChain.SOLANA -> throw IllegalArgumentException("solana is not secp256k1")
52
+ }
53
+
54
+ private fun hash160(data: ByteArray): ByteArray {
55
+ val sha = Bytes.sha256(data)
56
+ val d = RIPEMD160Digest()
57
+ d.update(sha, 0, sha.size)
58
+ return ByteArray(20).also { d.doFinal(it, 0) }
59
+ }
60
+
61
+ private fun secpAddress(chain: WalletChain, master: Bip32ECKeyPair, index: Int): String =
62
+ addressOfPrivateKey(chain, Bip32ECKeyPair.deriveKeyPair(master, secpPath(chain, index)).privateKey)
63
+
64
+ /** The secp256k1 private key of account [index] on [chain]. Callers wipe it. */
65
+ fun secpPrivateKey(secret: ByteArray, chain: WalletChain, index: Long): ByteArray {
66
+ if (!HdWallet.isAccountIndex(index)) throw walletError(WalletErrorCode.INVALID_REQUEST, "invalid account index")
67
+ if (chain == WalletChain.EVM) return HdWallet.privateKey(secret, index)
68
+ if (!HdWallet.isHdSecret(secret)) throw walletError(WalletErrorCode.ACCOUNT_UNAVAILABLE, "this wallet has only EVM account 0")
69
+ val seed = HdWallet.seedOf(secret)
70
+ try {
71
+ val node = Bip32ECKeyPair.deriveKeyPair(Bip32ECKeyPair.generateKeyPair(seed), secpPath(chain, index.toInt()))
72
+ return org.web3j.utils.Numeric.toBytesPadded(node.privateKey, 32)
73
+ } finally {
74
+ Bytes.wipe(seed)
75
+ }
76
+ }
77
+
78
+ /** Compressed secp256k1 public key of account [index] on [chain]. */
79
+ fun secpPublicKey(secret: ByteArray, chain: WalletChain, index: Long): ByteArray {
80
+ val key = secpPrivateKey(secret, chain, index)
81
+ try {
82
+ return compressedPublicKey(key)
83
+ } finally {
84
+ Bytes.wipe(key)
85
+ }
86
+ }
87
+
88
+ fun compressedPublicKey(privateKey: ByteArray): ByteArray =
89
+ Sign.publicPointFromPrivate(java.math.BigInteger(1, privateKey)).getEncoded(true)
90
+
91
+ fun hash160Of(data: ByteArray): ByteArray = hash160(data)
92
+
93
+ fun addressOfKey(chain: WalletChain, privateKey: ByteArray): String =
94
+ addressOfPrivateKey(chain, java.math.BigInteger(1, privateKey))
95
+
96
+ private fun addressOfPrivateKey(chain: WalletChain, privateKey: java.math.BigInteger): String {
97
+ val point = Sign.publicPointFromPrivate(privateKey)
98
+ val compressed = point.getEncoded(true)
99
+ val uncompressed = point.getEncoded(false)
100
+ return when (chain) {
101
+ WalletChain.EVM -> Keys.toChecksumAddress(Bytes.toHex(Hash.sha3(uncompressed.copyOfRange(1, 65)).copyOfRange(12, 32)))
102
+ WalletChain.TRON -> Base58.encodeCheck(byteArrayOf(0x41) + Hash.sha3(uncompressed.copyOfRange(1, 65)).copyOfRange(12, 32))
103
+ WalletChain.XRP -> Base58.encodeCheck(byteArrayOf(0x00) + hash160(compressed), Base58.XRP)
104
+ WalletChain.BITCOIN -> Bech32.segwitV0("bc", hash160(compressed))
105
+ WalletChain.BITCOIN_TESTNET -> Bech32.segwitV0("tb", hash160(compressed))
106
+ WalletChain.SOLANA -> throw IllegalArgumentException("solana is not secp256k1")
107
+ }
108
+ }
109
+
110
+ private fun hmacSha512(key: ByteArray, data: ByteArray): ByteArray =
111
+ Mac.getInstance("HmacSHA512").run { init(SecretKeySpec(key, "HmacSHA512")); doFinal(data) }
112
+
113
+ /** SLIP-0010 ed25519 derivation; every level is hardened. Callers wipe the 32-byte key. */
114
+ fun ed25519Key(seed: ByteArray, indices: IntArray): ByteArray {
115
+ var i = hmacSha512(Bytes.utf8("ed25519 seed"), seed)
116
+ var key = i.copyOfRange(0, 32)
117
+ var code = i.copyOfRange(32, 64)
118
+ for (index in indices) {
119
+ val hardened = index or H
120
+ val ser = byteArrayOf((hardened ushr 24).toByte(), (hardened ushr 16).toByte(), (hardened ushr 8).toByte(), hardened.toByte())
121
+ val data = byteArrayOf(0) + key + ser
122
+ Bytes.wipe(i)
123
+ i = hmacSha512(code, data)
124
+ Bytes.wipe(data, key, code)
125
+ key = i.copyOfRange(0, 32)
126
+ code = i.copyOfRange(32, 64)
127
+ }
128
+ Bytes.wipe(i, code)
129
+ return key
130
+ }
131
+
132
+ private fun solanaAddress(seed: ByteArray, index: Int): String {
133
+ val key = ed25519Key(seed, intArrayOf(44, 501, index, 0))
134
+ try {
135
+ return Base58.encode(Ed25519PrivateKeyParameters(key, 0).generatePublicKey().encoded)
136
+ } finally {
137
+ Bytes.wipe(key)
138
+ }
139
+ }
140
+
141
+ /** Addresses of accounts start..start+count-1 on [chain]. Only HD wallets have chains other than EVM account 0. */
142
+ fun addresses(secret: ByteArray, chain: WalletChain, start: Long, count: Int): List<String> {
143
+ if (!HdWallet.isAccountIndex(start) || count < 1 || !HdWallet.isAccountIndex(start + count - 1)) {
144
+ throw walletError(WalletErrorCode.INVALID_REQUEST, "invalid account range")
145
+ }
146
+ if (chain == WalletChain.EVM) return HdWallet.addresses(secret, start, count)
147
+ if (!HdWallet.isHdSecret(secret)) throw walletError(WalletErrorCode.ACCOUNT_UNAVAILABLE, "this wallet has only EVM account 0")
148
+ val seed = HdWallet.seedOf(secret)
149
+ try {
150
+ if (chain == WalletChain.SOLANA) return (0 until count).map { solanaAddress(seed, (start + it).toInt()) }
151
+ val master = Bip32ECKeyPair.generateKeyPair(seed)
152
+ return (0 until count).map { secpAddress(chain, master, (start + it).toInt()) }
153
+ } finally {
154
+ Bytes.wipe(seed)
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,192 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import android.app.Activity
5
+ import androidx.credentials.CreatePublicKeyCredentialRequest
6
+ import androidx.credentials.CreatePublicKeyCredentialResponse
7
+ import androidx.credentials.CredentialManager
8
+ import androidx.credentials.GetCredentialRequest
9
+ import androidx.credentials.GetPublicKeyCredentialOption
10
+ import androidx.credentials.PublicKeyCredential
11
+ import androidx.credentials.exceptions.CreateCredentialCancellationException
12
+ import androidx.credentials.exceptions.CreateCredentialException
13
+ import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
14
+ import androidx.credentials.exceptions.CreateCredentialUnsupportedException
15
+ import androidx.credentials.exceptions.GetCredentialCancellationException
16
+ import androidx.credentials.exceptions.GetCredentialException
17
+ import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
18
+ import androidx.credentials.exceptions.GetCredentialUnsupportedException
19
+ import androidx.credentials.exceptions.NoCredentialException
20
+ import androidx.credentials.exceptions.domerrors.DomError
21
+ import androidx.credentials.exceptions.domerrors.NotAllowedError
22
+ import androidx.credentials.exceptions.domerrors.NotSupportedError
23
+ import androidx.credentials.exceptions.domerrors.SecurityError
24
+ import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException
25
+ import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException
26
+ import kotlinx.coroutines.Dispatchers
27
+ import kotlinx.coroutines.withContext
28
+ import kotlinx.serialization.json.Json
29
+ import kotlinx.serialization.json.JsonObject
30
+ import kotlinx.serialization.json.JsonPrimitive
31
+ import kotlinx.serialization.json.addJsonObject
32
+ import kotlinx.serialization.json.booleanOrNull
33
+ import kotlinx.serialization.json.buildJsonObject
34
+ import kotlinx.serialization.json.contentOrNull
35
+ import kotlinx.serialization.json.intOrNull
36
+ import kotlinx.serialization.json.put
37
+ import kotlinx.serialization.json.putJsonArray
38
+ import kotlinx.serialization.json.putJsonObject
39
+
40
+ /**
41
+ * Backup passkeys through Credential Manager with WebAuthn JSON for RP `wallet.authon.dev`
42
+ * (the same RP as the web frame, so synced passkeys restore on either side). PRF is requested with
43
+ * `extensions.prf.eval.first` and read from `clientExtensionResults.prf.results.first`.
44
+ *
45
+ * The app must be listed in the RP's Digital Asset Links (`get_login_creds`), which the API
46
+ * publishes only for approved app registrations.
47
+ */
48
+ class CredentialManagerPasskeys(
49
+ private val activityProvider: () -> Activity?,
50
+ private val rpId: String = WalletApi.DEFAULT_RP_ID,
51
+ ) : PasskeyProvider {
52
+
53
+ private fun activity(): Activity = activityProvider()?.takeUnless { it.isFinishing || it.isDestroyed }
54
+ ?: throw walletError(WalletErrorCode.INTERNAL, "no foreground activity for the passkey prompt")
55
+
56
+ override suspend fun create(userId: ByteArray, userName: String, salt: ByteArray): PasskeyCreateResult {
57
+ val request = buildJsonObject {
58
+ put("challenge", Bytes.toBase64Url(Bytes.randomBytes(32)))
59
+ putJsonObject("rp") {
60
+ put("id", rpId)
61
+ put("name", "Authon Wallet")
62
+ }
63
+ putJsonObject("user") {
64
+ put("id", Bytes.toBase64Url(userId))
65
+ put("name", userName)
66
+ put("displayName", userName)
67
+ }
68
+ putJsonArray("pubKeyCredParams") {
69
+ for (alg in listOf(-7, -257, -8)) addJsonObject {
70
+ put("type", "public-key")
71
+ put("alg", alg)
72
+ }
73
+ }
74
+ put("attestation", "none")
75
+ putJsonObject("authenticatorSelection") {
76
+ put("residentKey", "preferred")
77
+ put("userVerification", "required")
78
+ }
79
+ prfExtension(salt)
80
+ }
81
+ val activity = activity()
82
+ val response = try {
83
+ withContext(Dispatchers.Main) {
84
+ CredentialManager.create(activity).createCredential(activity, CreatePublicKeyCredentialRequest(request.toString()))
85
+ }
86
+ } catch (e: CreateCredentialException) {
87
+ throw mapCreate(e)
88
+ }
89
+ val json = (response as? CreatePublicKeyCredentialResponse)?.registrationResponseJson
90
+ ?: throw walletError(WalletErrorCode.INTERNAL, "unexpected credential response")
91
+ val cred = Json.parseToJsonElement(json) as JsonObject
92
+ val ext = cred["clientExtensionResults"] as? JsonObject
93
+ val prf = ext?.get("prf") as? JsonObject
94
+ if (prf == null || (prf["enabled"] as? JsonPrimitive)?.booleanOrNull == false) throw walletError(WalletErrorCode.PRF_UNSUPPORTED)
95
+ val credentialId = normalize(cred.str("rawId") ?: cred.str("id") ?: throw walletError(WalletErrorCode.INTERNAL, "credential id missing"))
96
+ val resp = cred["response"] as? JsonObject ?: JsonObject(emptyMap())
97
+ val alg = (resp["publicKeyAlgorithm"] as? JsonPrimitive)?.intOrNull
98
+ val spki = resp.str("publicKey")?.takeIf { alg == -7 || alg == -257 || alg == -8 }?.let { normalize(it) }
99
+ val attestation = resp.str("attestationObject")?.let { normalize(it) }
100
+ val direct = prfFirst(prf)
101
+ val prfOutput = direct ?: get(credentialId, salt, null).prfOutput
102
+ return PasskeyCreateResult(credentialId, prfOutput, spki, if (spki != null) alg else null, attestation)
103
+ }
104
+
105
+ override suspend fun get(credentialId: String, salt: ByteArray, challenge: ByteArray?): PasskeyGetResult {
106
+ val request = buildJsonObject {
107
+ put("challenge", Bytes.toBase64Url(challenge ?: Bytes.randomBytes(32)))
108
+ put("rpId", rpId)
109
+ putJsonArray("allowCredentials") {
110
+ addJsonObject {
111
+ put("type", "public-key")
112
+ put("id", normalize(credentialId))
113
+ }
114
+ }
115
+ put("userVerification", "required")
116
+ prfExtension(salt)
117
+ }
118
+ val activity = activity()
119
+ val result = try {
120
+ withContext(Dispatchers.Main) {
121
+ CredentialManager.create(activity).getCredential(
122
+ activity,
123
+ GetCredentialRequest(listOf(GetPublicKeyCredentialOption(request.toString()))),
124
+ )
125
+ }
126
+ } catch (e: GetCredentialException) {
127
+ throw mapGet(e)
128
+ }
129
+ val json = (result.credential as? PublicKeyCredential)?.authenticationResponseJson
130
+ ?: throw walletError(WalletErrorCode.INTERNAL, "unexpected credential response")
131
+ val cred = Json.parseToJsonElement(json) as JsonObject
132
+ val prf = (cred["clientExtensionResults"] as? JsonObject)?.get("prf") as? JsonObject
133
+ val out = prf?.let { prfFirst(it) } ?: throw walletError(WalletErrorCode.PRF_UNSUPPORTED)
134
+ val resp = cred["response"] as? JsonObject ?: JsonObject(emptyMap())
135
+ fun field(name: String) = resp.str(name)?.let { normalize(it) } ?: ""
136
+ return PasskeyGetResult(
137
+ out,
138
+ PasskeyAssertion(
139
+ credentialId = normalize(cred.str("rawId") ?: cred.str("id") ?: credentialId),
140
+ clientDataJSON = field("clientDataJSON"),
141
+ authenticatorData = field("authenticatorData"),
142
+ signature = field("signature"),
143
+ ),
144
+ )
145
+ }
146
+
147
+ private fun kotlinx.serialization.json.JsonObjectBuilder.prfExtension(salt: ByteArray) {
148
+ putJsonObject("extensions") {
149
+ putJsonObject("prf") {
150
+ putJsonObject("eval") { put("first", Bytes.toBase64Url(salt)) }
151
+ }
152
+ }
153
+ }
154
+
155
+ private fun prfFirst(prf: JsonObject): ByteArray? {
156
+ val first = ((prf["results"] as? JsonObject)?.get("first") as? JsonPrimitive)?.contentOrNull ?: return null
157
+ val bytes = runCatching { Bytes.fromBase64(first) }.getOrNull() ?: return null
158
+ return bytes.takeIf { it.size == 32 }
159
+ }
160
+
161
+ private fun normalize(b64: String): String = Bytes.toBase64Url(Bytes.fromBase64(b64))
162
+
163
+ private fun JsonObject.str(k: String) = (this[k] as? JsonPrimitive)?.takeIf { it.isString }?.content
164
+
165
+ private fun mapDom(error: DomError, cause: Exception): WalletException = when (error) {
166
+ is NotAllowedError -> WalletException(WalletErrorCode.USER_CANCELLED, cause.message, cause)
167
+ is SecurityError -> WalletException(
168
+ WalletErrorCode.ORIGIN_NOT_ALLOWED,
169
+ "this app is not linked to $rpId (Digital Asset Links): ${cause.message}",
170
+ cause,
171
+ )
172
+ is NotSupportedError -> WalletException(WalletErrorCode.PRF_UNSUPPORTED, cause.message, cause)
173
+ else -> WalletException(WalletErrorCode.INTERNAL, "passkey failure", cause)
174
+ }
175
+
176
+ private fun mapCreate(e: CreateCredentialException): WalletException = when (e) {
177
+ is CreateCredentialCancellationException -> WalletException(WalletErrorCode.USER_CANCELLED, e.message, e)
178
+ is CreateCredentialUnsupportedException, is CreateCredentialProviderConfigurationException ->
179
+ WalletException(WalletErrorCode.PRF_UNSUPPORTED, e.message, e)
180
+ is CreatePublicKeyCredentialDomException -> mapDom(e.domError, e)
181
+ else -> WalletException(WalletErrorCode.INTERNAL, "passkey failure", e)
182
+ }
183
+
184
+ private fun mapGet(e: GetCredentialException): WalletException = when (e) {
185
+ is GetCredentialCancellationException -> WalletException(WalletErrorCode.USER_CANCELLED, e.message, e)
186
+ is NoCredentialException -> WalletException(WalletErrorCode.USER_CANCELLED, "no matching backup passkey", e)
187
+ is GetCredentialUnsupportedException, is GetCredentialProviderConfigurationException ->
188
+ WalletException(WalletErrorCode.PRF_UNSUPPORTED, e.message, e)
189
+ is GetPublicKeyCredentialDomException -> mapDom(e.domError, e)
190
+ else -> WalletException(WalletErrorCode.INTERNAL, "passkey failure", e)
191
+ }
192
+ }
@@ -0,0 +1,17 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Der.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import java.math.BigInteger
5
+
6
+ /** DER encoding of an ECDSA (r, s) signature. */
7
+ internal object Der {
8
+ private fun int(v: ByteArray): ByteArray {
9
+ val n = BigInteger(1, v).toByteArray() // minimal, with a leading 0 when the high bit is set
10
+ return byteArrayOf(0x02, n.size.toByte()) + n
11
+ }
12
+
13
+ fun encode(r: ByteArray, s: ByteArray): ByteArray {
14
+ val body = int(r) + int(s)
15
+ return byteArrayOf(0x30, body.size.toByte()) + body
16
+ }
17
+ }
@@ -0,0 +1,170 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/DeviceStore.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import java.math.BigInteger
5
+ import java.security.KeyPairGenerator
6
+ import java.security.PrivateKey
7
+ import java.security.Signature
8
+ import java.security.interfaces.ECPublicKey
9
+ import java.security.spec.ECGenParameterSpec
10
+ import java.util.concurrent.ConcurrentHashMap
11
+ import javax.crypto.Cipher
12
+ import javax.crypto.KeyGenerator
13
+ import javax.crypto.SecretKey
14
+ import javax.crypto.spec.GCMParameterSpec
15
+
16
+ class SealedShare(val iv: ByteArray, val ciphertext: ByteArray)
17
+
18
+ /**
19
+ * The non-exportable keys of one device record: a P-256 signing key (its public JWK is registered
20
+ * with the API) and an AES-256-GCM key that seals the device share D.
21
+ */
22
+ interface DeviceKeys {
23
+ /** Identifies the keys inside their store (e.g. the Keystore alias prefix). */
24
+ val handle: String
25
+ val publicJwk: DevicePublicJwk
26
+
27
+ /** ECDSA P-256 / SHA-256 over [data]; raw r||s (64 bytes) like WebCrypto. */
28
+ fun sign(data: ByteArray): ByteArray
29
+
30
+ fun seal(plain: ByteArray, aad: ByteArray): SealedShare
31
+
32
+ fun open(sealed: SealedShare, aad: ByteArray): ByteArray
33
+ }
34
+
35
+ data class DeviceRecord(
36
+ /** `projectId \0 userId`. */
37
+ val key: String,
38
+ val keys: DeviceKeys,
39
+ val iv: ByteArray,
40
+ val ciphertext: ByteArray,
41
+ val deviceId: String,
42
+ val walletId: String,
43
+ val address: String,
44
+ val shareVersion: Int,
45
+ val createdAt: Long,
46
+ )
47
+
48
+ /** Where the device share lives. Only D (sealed) and key references are stored; never k, A or B. */
49
+ interface DeviceStore {
50
+ suspend fun generateKeys(): DeviceKeys
51
+
52
+ /** Removes keys that never made it into a stored record. */
53
+ suspend fun discardKeys(keys: DeviceKeys)
54
+
55
+ suspend fun get(key: String): DeviceRecord?
56
+
57
+ /** Replaces the record for [DeviceRecord.key]; keys of a replaced record are removed. */
58
+ suspend fun put(record: DeviceRecord)
59
+
60
+ suspend fun delete(key: String)
61
+ }
62
+
63
+ internal object DeviceStorage {
64
+ fun storageKey(projectId: String, userId: String): String = "$projectId\u0000$userId"
65
+
66
+ /** DER ECDSA signature -> raw r||s, each left-padded to [size] bytes. */
67
+ fun derToRaw(der: ByteArray, size: Int = 32): ByteArray {
68
+ var i = 0
69
+ fun readLen(): Int {
70
+ var len = der[i++].toInt() and 0xff
71
+ if (len and 0x80 != 0) {
72
+ val n = len and 0x7f
73
+ len = 0
74
+ repeat(n) { len = (len shl 8) or (der[i++].toInt() and 0xff) }
75
+ }
76
+ return len
77
+ }
78
+ require(der[i++].toInt() == 0x30) { "not a DER sequence" }
79
+ readLen()
80
+ fun readInt(): ByteArray {
81
+ require(der[i++].toInt() == 0x02) { "not a DER integer" }
82
+ val len = readLen()
83
+ val v = der.copyOfRange(i, i + len)
84
+ i += len
85
+ return v
86
+ }
87
+ val r = readInt()
88
+ val s = readInt()
89
+ val out = ByteArray(size * 2)
90
+ fun place(v: ByteArray, offset: Int) {
91
+ val trimmed = v.dropWhile { it.toInt() == 0 }.toByteArray()
92
+ require(trimmed.size <= size) { "integer too long" }
93
+ trimmed.copyInto(out, offset + size - trimmed.size)
94
+ }
95
+ place(r, 0)
96
+ place(s, size)
97
+ return out
98
+ }
99
+
100
+ fun unsigned32(v: BigInteger): ByteArray {
101
+ val raw = v.toByteArray()
102
+ val trimmed = if (raw.size > 32) raw.copyOfRange(raw.size - 32, raw.size) else raw
103
+ return ByteArray(32 - trimmed.size) + trimmed
104
+ }
105
+
106
+ fun jwkOf(key: ECPublicKey): DevicePublicJwk =
107
+ DevicePublicJwk(Bytes.toBase64Url(unsigned32(key.w.affineX)), Bytes.toBase64Url(unsigned32(key.w.affineY)))
108
+
109
+ fun gcmSeal(key: SecretKey, plain: ByteArray, aad: ByteArray, iv: ByteArray? = null): SealedShare {
110
+ val cipher = Cipher.getInstance("AES/GCM/NoPadding")
111
+ if (iv == null) cipher.init(Cipher.ENCRYPT_MODE, key) else cipher.init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(128, iv))
112
+ cipher.updateAAD(aad)
113
+ val ct = cipher.doFinal(plain)
114
+ return SealedShare(cipher.iv, ct)
115
+ }
116
+
117
+ fun gcmOpen(key: SecretKey, sealed: SealedShare, aad: ByteArray): ByteArray {
118
+ val cipher = Cipher.getInstance("AES/GCM/NoPadding")
119
+ cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(128, sealed.iv))
120
+ cipher.updateAAD(aad)
121
+ return cipher.doFinal(sealed.ciphertext)
122
+ }
123
+ }
124
+
125
+ /**
126
+ * In-memory store with software JCA keys. For JVM tests and emulators only: nothing survives the
127
+ * process and the keys are not hardware-backed.
128
+ */
129
+ class InMemoryDeviceStore : DeviceStore {
130
+ private val records = ConcurrentHashMap<String, DeviceRecord>()
131
+ private var counter = 0
132
+
133
+ private class SoftwareKeys(override val handle: String) : DeviceKeys {
134
+ private val signKey: PrivateKey
135
+ private val aesKey: SecretKey = KeyGenerator.getInstance("AES").apply { init(256) }.generateKey()
136
+ override val publicJwk: DevicePublicJwk
137
+
138
+ init {
139
+ val pair = KeyPairGenerator.getInstance("EC").apply { initialize(ECGenParameterSpec("secp256r1")) }.generateKeyPair()
140
+ signKey = pair.private
141
+ publicJwk = DeviceStorage.jwkOf(pair.public as ECPublicKey)
142
+ }
143
+
144
+ override fun sign(data: ByteArray): ByteArray {
145
+ val s = Signature.getInstance("SHA256withECDSA")
146
+ s.initSign(signKey)
147
+ s.update(data)
148
+ return DeviceStorage.derToRaw(s.sign())
149
+ }
150
+
151
+ override fun seal(plain: ByteArray, aad: ByteArray): SealedShare =
152
+ DeviceStorage.gcmSeal(aesKey, plain, aad, Bytes.randomBytes(12))
153
+
154
+ override fun open(sealed: SealedShare, aad: ByteArray): ByteArray = DeviceStorage.gcmOpen(aesKey, sealed, aad)
155
+ }
156
+
157
+ override suspend fun generateKeys(): DeviceKeys = synchronized(this) { SoftwareKeys("mem-${++counter}") }
158
+
159
+ override suspend fun discardKeys(keys: DeviceKeys) = Unit
160
+
161
+ override suspend fun get(key: String): DeviceRecord? = records[key]
162
+
163
+ override suspend fun put(record: DeviceRecord) {
164
+ records[record.key] = record
165
+ }
166
+
167
+ override suspend fun delete(key: String) {
168
+ records.remove(key)
169
+ }
170
+ }
@@ -0,0 +1,141 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import android.app.Activity
5
+ import android.app.AlertDialog
6
+ import android.content.Context
7
+ import android.graphics.Color
8
+ import android.graphics.Typeface
9
+ import android.hardware.biometrics.BiometricManager
10
+ import android.hardware.biometrics.BiometricPrompt
11
+ import android.os.Build
12
+ import android.os.CancellationSignal
13
+ import android.util.TypedValue
14
+ import android.view.ViewGroup
15
+ import android.widget.LinearLayout
16
+ import android.widget.ScrollView
17
+ import android.widget.TextView
18
+ import kotlinx.coroutines.Dispatchers
19
+ import kotlinx.coroutines.suspendCancellableCoroutine
20
+ import kotlinx.coroutines.withContext
21
+ import kotlin.coroutines.resume
22
+
23
+ /**
24
+ * Default approval UI: a platform AlertDialog with the summary (rows, warnings, the full text).
25
+ * "Sign" is enabled only after the text was scrolled to the end, like the web approval card.
26
+ */
27
+ class DialogWalletApprover(private val activityProvider: () -> Activity?) : WalletApprover {
28
+
29
+ override suspend fun approve(summary: SignSummary): Boolean = withContext(Dispatchers.Main) {
30
+ val activity = activityProvider()?.takeUnless { it.isFinishing || it.isDestroyed }
31
+ ?: throw walletError(WalletErrorCode.CONFIRM_WINDOW_BLOCKED, "no foreground activity for the approval")
32
+ suspendCancellableCoroutine { cont ->
33
+ val scroll = ScrollView(activity)
34
+ scroll.addView(content(activity, summary))
35
+ var answered = false
36
+ val dialog = AlertDialog.Builder(activity)
37
+ .setTitle(summary.title)
38
+ .setView(scroll)
39
+ .setPositiveButton("Sign") { _, _ ->
40
+ answered = true
41
+ if (cont.isActive) cont.resume(true)
42
+ }
43
+ .setNegativeButton("Cancel") { _, _ ->
44
+ answered = true
45
+ if (cont.isActive) cont.resume(false)
46
+ }
47
+ .setOnDismissListener {
48
+ if (!answered && cont.isActive) cont.resume(false)
49
+ }
50
+ .create()
51
+ dialog.setCanceledOnTouchOutside(false)
52
+ dialog.setOnShowListener {
53
+ val sign = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
54
+ fun update() {
55
+ sign.isEnabled = !scroll.canScrollVertically(1)
56
+ }
57
+ update()
58
+ scroll.setOnScrollChangeListener { _, _, _, _, _ -> if (!sign.isEnabled) update() }
59
+ scroll.post { update() }
60
+ }
61
+ cont.invokeOnCancellation { dialog.dismiss() }
62
+ dialog.show()
63
+ }
64
+ }
65
+
66
+ private fun content(context: Context, summary: SignSummary): LinearLayout {
67
+ val pad = dp(context, 20)
68
+ val layout = LinearLayout(context).apply {
69
+ orientation = LinearLayout.VERTICAL
70
+ setPadding(pad, dp(context, 8), pad, dp(context, 8))
71
+ }
72
+ for (w in summary.warnings) {
73
+ layout.addView(text(context, w).apply { setTextColor(Color.rgb(0xb4, 0x23, 0x18)); setPadding(0, 0, 0, dp(context, 8)) })
74
+ }
75
+ for (row in summary.rows) {
76
+ layout.addView(text(context, row.label).apply { alpha = 0.7f; setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) })
77
+ layout.addView(
78
+ text(context, row.value).apply {
79
+ if (row.mono) typeface = Typeface.MONOSPACE
80
+ setTextIsSelectable(true)
81
+ setPadding(0, 0, 0, dp(context, 8))
82
+ },
83
+ )
84
+ }
85
+ summary.body?.let { body ->
86
+ layout.addView(
87
+ text(context, body).apply {
88
+ typeface = Typeface.MONOSPACE
89
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
90
+ setTextIsSelectable(true)
91
+ },
92
+ )
93
+ }
94
+ return layout
95
+ }
96
+
97
+ private fun text(context: Context, value: String) = TextView(context).apply {
98
+ text = value
99
+ layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
100
+ }
101
+
102
+ private fun dp(context: Context, v: Int) = (v * context.resources.displayMetrics.density).toInt()
103
+ }
104
+
105
+ /**
106
+ * Device lock check with the platform BiometricPrompt: biometrics or the device credential on
107
+ * Android 10+, biometrics only on Android 9.
108
+ */
109
+ class BiometricDeviceAuthenticator(private val activityProvider: () -> Activity?) : DeviceAuthenticator {
110
+
111
+ override suspend fun authenticate(reason: String): Boolean = withContext(Dispatchers.Main) {
112
+ val activity = activityProvider()?.takeUnless { it.isFinishing || it.isDestroyed }
113
+ ?: throw walletError(WalletErrorCode.CONFIRM_WINDOW_BLOCKED, "no foreground activity for device authentication")
114
+ suspendCancellableCoroutine { cont ->
115
+ val executor = activity.mainExecutor
116
+ val builder = BiometricPrompt.Builder(activity).setTitle(reason).setSubtitle("Confirm it is you")
117
+ when {
118
+ Build.VERSION.SDK_INT >= 30 -> builder.setAllowedAuthenticators(
119
+ BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL,
120
+ )
121
+ Build.VERSION.SDK_INT == 29 -> @Suppress("DEPRECATION") builder.setDeviceCredentialAllowed(true)
122
+ else -> builder.setNegativeButton("Cancel", executor) { _, _ -> if (cont.isActive) cont.resume(false) }
123
+ }
124
+ val signal = CancellationSignal()
125
+ cont.invokeOnCancellation { signal.cancel() }
126
+ builder.build().authenticate(
127
+ signal,
128
+ executor,
129
+ object : BiometricPrompt.AuthenticationCallback() {
130
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
131
+ if (cont.isActive) cont.resume(true)
132
+ }
133
+
134
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
135
+ if (cont.isActive) cont.resume(false)
136
+ }
137
+ },
138
+ )
139
+ }
140
+ }
141
+ }