@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # @authon/react-native-wallet
2
+
3
+ ## 0.4.0
4
+
5
+ - `signChainTransaction({ chain, account, address, payload })` for app-built transactions on other chains (NATIVE.md section 12): Solana (`{ message }` → `{ signature }`), TRON (`{ rawDataHex }` → `{ txID, signature }`), Bitcoin and Bitcoin testnet (`{ psbt }` → `{ psbt, txHex? }`) and XRP (`{ txBlobHex }` → `{ signature }`). EVM stays on `signTransaction` (`INVALID_PARAMS` for `chain: 'evm'`). The native approval UI summarizes the transaction before the key is assembled.
6
+ - `getAccounts` passes through `publicKey` when the native wallet returns one (XRP).
7
+
8
+ ## 0.3.0
9
+
10
+ - `getAccounts({ start, count, chain })`: account addresses on `evm` (default), `tron`, `xrp`, `bitcoin`, `bitcoin-testnet` and `solana`, derived from the same seed with the standard paths. Each account carries its `chain`; a native wallet that answers without it is refused (`UNSUPPORTED_TYPE`). Signing stays EVM only.
11
+
12
+ ## 0.2.0
13
+
14
+ - `capabilities()`: checks before sign-in whether this device and app can use the wallet (app registration and approval, wallet enabled, hardware key storage, device lock, passkey backup support), with reason codes and the app-config `diagnosticId`. No session needed.
15
+ - `AuthonWalletError` has `diagnosticId` (server request ID) and `retryable`, passed through from the Swift and Kotlin wallets.
16
+ - iOS: a second signature right after the first no longer hangs; the approval sheet fails with `CONFIRM_WINDOW_BLOCKED` when it cannot be shown.
17
+
18
+ ## 0.1.0
19
+
20
+ - First release: Expo module over the Swift and Kotlin embedded wallets (status, create/ensure, accounts, personal_sign, EIP-712, transactions, passkey protect/upgrade/restore, device revoke), `useAuthonWallet()` with `@authon/react-native`.
package/README.md CHANGED
@@ -1,3 +1,252 @@
1
- # Temporary Holding Version
1
+ # @authon/react-native-wallet
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > The Authon embedded wallet for React Native and Expo: an Expo native module around the Swift
4
+ > (`AuthonWallet`) and Kotlin (`dev.authon.sdk.wallet`) wallet implementations.
5
+
6
+ [![License](https://img.shields.io/badge/license-MIT-blue)](../../LICENSE)
7
+ [![Version](https://img.shields.io/badge/version-0.4.0-blue)]()
8
+
9
+ It is the same wallet as the web SDK (`@authon/js`): same wallet row, same `/v1/wallet/me/*` API,
10
+ same shares. A user gets the same address in your web app and your mobile app, and the app install
11
+ appears as one more device in the wallet's device list.
12
+
13
+ - The device keeps only its share D, sealed by a Secure Enclave (iOS) or Android Keystore key. Every
14
+ signature fetches the company share A with a device-key proof, combines the shares in native memory,
15
+ signs and wipes the key. The full key is never stored.
16
+ - **No secret crosses the JS bridge.** D, A, the backup share B, the key and the passkey PRF output stay
17
+ in Swift / Kotlin. JS receives addresses, signatures, wallet status and the device list only.
18
+ - Signing is confirmed with the SDKs' native approval UI (a SwiftUI sheet on iOS, a dialog on
19
+ Android) when the project requires confirmation (the default, a server-side setting).
20
+ - Passkey backup and restore use the RP ID `wallet.authon.dev`, so a backup made on the web restores
21
+ in the app and the other way round.
22
+
23
+ ## Requirements
24
+
25
+ - **An Expo development build or a bare React Native app.** Expo Go does not contain this native
26
+ module (calls fail with `NOT_CONFIGURED`). Expo SDK 53+ / React Native 0.76+.
27
+ - iOS 16+ (iOS 18+ for passkey backup and restore, which need the passkey PRF extension).
28
+ - Android minSdk 28, compileSdk 35+. Passkey backup and restore need Google Play services with a
29
+ passkey provider that supports PRF (Google Password Manager on Android 9+ with current Play services).
30
+ - Signing: EVM (personal_sign, EIP-712 v4, legacy / EIP-2930 / EIP-1559 transactions), HD accounts
31
+ `m/44'/60'/0'/0/n`.
32
+ - Addresses (receive / display) from the same seed: TRON `m/44'/195'/n'/0/0`, XRP `m/44'/144'/n'/0/0`,
33
+ Bitcoin P2WPKH `m/84'/0'/n'/0/0` (testnet `m/84'/1'/n'/0/0`), Solana `m/44'/501'/n'/0'`. These
34
+ chains sign through `signChainTransaction` (the app builds the transaction; fees, balances and broadcast are up to the app). A wallet created before HD wallets has EVM account 0 only
35
+ (`ACCOUNT_UNAVAILABLE` for other chains).
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ npx expo install @authon/react-native-wallet expo-build-properties
41
+ ```
42
+
43
+ ```json
44
+ // app.json
45
+ {
46
+ "expo": {
47
+ "ios": {
48
+ "bundleIdentifier": "com.example.app",
49
+ "associatedDomains": ["webcredentials:wallet.authon.dev"]
50
+ },
51
+ "android": { "package": "com.example.app" },
52
+ "plugins": [
53
+ ["expo-build-properties", { "ios": { "deploymentTarget": "16.0" }, "android": { "minSdkVersion": 28 } }]
54
+ ]
55
+ }
56
+ }
57
+ ```
58
+
59
+ Then build a development build: `npx expo prebuild` and `npx expo run:ios` / `npx expo run:android`
60
+ (or EAS Build). Rebuild the app after installing or updating the package.
61
+
62
+ ## Register the app in the Authon console
63
+
64
+ The wallet API only serves apps registered for the project, and passkeys only work for apps an
65
+ Authon administrator has approved:
66
+
67
+ 1. Authon console, Project, Wallet, **Native apps**: add
68
+ - iOS: your Apple **Team ID** and **bundle ID** (the app ID is `TEAMID.bundleId`),
69
+ - Android: the **package name** and the **SHA-256 fingerprint of your release signing
70
+ certificate** (for Play App Signing, the app signing key from Play Console, not the upload key).
71
+ 2. Wait for approval. Adding or changing an entry resets it to "not approved". Until it is approved,
72
+ `appConfig().approved` is false, and `protect()`, `upgradeBackup()` and `restore()` fail with
73
+ `ORIGIN_NOT_ALLOWED`. Signing and accounts work as soon as the app is registered.
74
+ 3. After approval, wallet.authon.dev publishes your app in `/.well-known/apple-app-site-association`
75
+ (`webcredentials`) and `/.well-known/assetlinks.json` (`get_login_creds`). The iOS app needs the
76
+ Associated Domains entitlement `webcredentials:wallet.authon.dev` (above).
77
+
78
+ ## Usage
79
+
80
+ ### With @authon/react-native
81
+
82
+ Inside `<AuthonProvider>` the hook takes the session token from the provider and follows sign-in and
83
+ sign-out:
84
+
85
+ ```tsx
86
+ import { AuthonProvider } from '@authon/react-native';
87
+ import { useAuthonWallet } from '@authon/react-native-wallet';
88
+
89
+ function WalletScreen() {
90
+ const { wallet, current, isLoaded, error } = useAuthonWallet({ publishableKey: 'pk_live_...' });
91
+
92
+ if (!isLoaded) return <Spinner />;
93
+ if (!current) return <Button title="Create wallet" onPress={() => wallet.ensure()} />;
94
+ return (
95
+ <>
96
+ <Text>{current.address}</Text>
97
+ {!current.protected && <Button title="Back up with a passkey" onPress={() => wallet.protect()} />}
98
+ <Button title="Sign" onPress={() => wallet.signMessage('Hello from Authon')} />
99
+ </>
100
+ );
101
+ }
102
+
103
+ export default function App() {
104
+ return (
105
+ <AuthonProvider publishableKey="pk_live_...">
106
+ <WalletScreen />
107
+ </AuthonProvider>
108
+ );
109
+ }
110
+ ```
111
+
112
+ ### Without the provider
113
+
114
+ ```ts
115
+ import { createAuthonWallet } from '@authon/react-native-wallet';
116
+
117
+ const wallet = createAuthonWallet({
118
+ publishableKey: 'pk_live_...',
119
+ getToken: async () => mySession.accessToken, // the Authon session token, refreshed when needed
120
+ });
121
+
122
+ const { address } = await wallet.ensure();
123
+ const signature = await wallet.signMessage('Hello');
124
+ ```
125
+
126
+ `getToken` is called before every wallet call and the token is passed to native code for that call
127
+ only. The token is the Authon session token, not a wallet secret. Call `wallet.dispose()` on sign-out.
128
+
129
+ ## API
130
+
131
+ Method names and result shapes follow `@authon/js` (`WalletClient`).
132
+
133
+ | Method | Description |
134
+ |---|---|
135
+ | `capabilities()` | Pre-sign-in check of this device and app (see below). No session, creates nothing, opens no UI. |
136
+ | `appConfig()` | App registration and wallet settings: `{ projectId, allowed, approved, enabled, chainIds, confirmSigning, rpId, minFrameProtocol }`. No session needed. |
137
+ | `get()` / `status()` | The signed-in user's wallet `{ address, protected, needsRestore, backupUpgradeRecommended, devices }`, or `null` when there is none. |
138
+ | `create()` | Creates the wallet (or returns the existing one). |
139
+ | `ensure()` | `get()`, then `create()` when there is no wallet. Concurrent calls share one request. |
140
+ | `isProtected()` | True when the wallet has a passkey backup. |
141
+ | `getAccounts({ start, count, chain })` | HD account addresses `{ index, path, address, chain }` (public, no approval). `chain`: `evm` (default), `tron`, `xrp`, `bitcoin`, `bitcoin-testnet`, `solana`. |
142
+ | `signMessage(message, { account })` | EIP-191 personal_sign; `message` is text or `{ raw: '0x...' }`. |
143
+ | `signTypedData(typedData, { account })` | EIP-712 v4. |
144
+ | `signChainTransaction({ chain, account, address, payload })` | Signs an app-built transaction: Solana `{ message }` (base64 Message) → `{ signature }` (base58); TRON `{ rawDataHex }` → `{ txID, signature }`; Bitcoin / testnet `{ psbt }` (base64, every input with witness_utxo; only our P2WPKH inputs are signed) → `{ psbt, txHex? }` (txHex when every input is ours); XRP `{ txBlobHex }` (encodeForSigning output with SigningPubKey = `getAccounts({ chain: 'xrp' })` `publicKey`) → `{ signature }` (DER). The approval UI summarizes it; unknown programs are shown as high risk. `address` must be the account's address on that chain (`ADDRESS_MISMATCH` otherwise). |
145
+ | `signTransaction(tx, { account })` | Legacy, EIP-2930 or EIP-1559; returns the serialized signed transaction. `bigint` values are allowed. |
146
+ | `protect()` | Creates the passkey backup. Do not let users receive funds before the wallet is protected. |
147
+ | `upgradeBackup()` | Replaces a legacy backup (`backupUpgradeRecommended`) with a verifiable one. |
148
+ | `restore({ resetOtherDevices })` | Restores on this install with the backup passkey; `resetOtherDevices: true` revokes every other device. |
149
+ | `revokeDevice(deviceId)` | Revokes a device. |
150
+ | `onChange(listener)` | Called with the wallet after create/protect/restore/revoke, and `null` on dispose. |
151
+ | `provider(chainId)` | Minimal EIP-1193 provider: `eth_chainId`, `eth_accounts`, `eth_requestAccounts`, `personal_sign`, `eth_signTypedData_v4`, `eth_signTransaction`. |
152
+ | `dispose()` | Releases the native instance and rejects calls in flight (`DISPOSED`). The device share stays on the device. |
153
+
154
+ `account` (default 0) selects the HD account. For accounts other than 0 the client first reads the
155
+ account's address and the native wallet refuses to sign when the derived key does not match it.
156
+
157
+ Options of `createAuthonWallet` / `useAuthonWallet`:
158
+
159
+ | Option | Description |
160
+ |---|---|
161
+ | `publishableKey` | Project publishable key. |
162
+ | `apiUrl` | Defaults to `https://api.authon.dev`. |
163
+ | `getToken` | Session token getter (optional with the hook inside `<AuthonProvider>`). |
164
+ | `appId` | `TEAMID.bundleId` (iOS) / package name (Android), or `{ ios, android }`. Detected when omitted. |
165
+ | `requireDeviceAuthentication` | Also ask for Face ID / Touch ID / passcode (iOS) or BiometricPrompt (Android) before each signature. |
166
+ | `testing` | **Test only**, refused outside debug builds: `{ allowSoftwareKeys, autoApprove }` for simulators and emulators (software device keys instead of Secure Enclave / StrongBox, and no approval UI). Never ship it. |
167
+
168
+ ### Before sign-in: `capabilities()`
169
+
170
+ ```ts
171
+ const caps = await wallet.capabilities();
172
+ if (caps.ready === 'unavailable') showWalletUnavailable(caps); // tell the user before sign-in
173
+ // 'unknown': continue, but be ready for that feature to fail
174
+ ```
175
+
176
+ `{ ready, app, wallet, secureStorage, deviceLock, passkeyBackup, diagnosticId }`; each check is
177
+ `{ status: 'available' | 'unavailable' | 'unknown', reason }`. `ready` is `unavailable` if any check is,
178
+ else `unknown` if any is, else `available`. It answers for a release build (the `testing` options are
179
+ ignored).
180
+
181
+ | Check | unavailable reason | unknown reason |
182
+ |---|---|---|
183
+ | `app` | `APP_NOT_REGISTERED`, `APP_SIGNATURE_MISMATCH` (Android: the running app's signing certificate differs from the registered one) | `APP_NOT_APPROVED` (signing works, passkey backup waits for approval), `NETWORK_ERROR`, `API_ERROR` |
184
+ | `wallet` | `WALLET_DISABLED` | `NETWORK_ERROR` |
185
+ | `secureStorage` | `HARDWARE_KEYSTORE_UNAVAILABLE` | `SIMULATOR` (simulators and emulators never count as hardware protected), `CHECK_FAILED` |
186
+ | `deviceLock` | `DEVICE_LOCK_NOT_SET` | `CHECK_FAILED` |
187
+ | `passkeyBackup` | `OS_UNSUPPORTED` (iOS < 18, Android < 9), `PASSKEY_API_UNAVAILABLE` | `PRF_PROVIDER_UNVERIFIED` (the provider's PRF support is confirmed on the first `protect()`) |
188
+
189
+ ### Errors
190
+
191
+ Every failure is an `AuthonWalletError` with `code`, `message`, `diagnosticId` (the server's request ID
192
+ when an API response caused it; log it, never secrets) and `retryable`. Codes are the web wallet's
193
+ (`NOT_FOUND`, `NEEDS_RESTORE`, `NOT_PROTECTED`, `PRF_UNSUPPORTED`, `ORIGIN_NOT_ALLOWED`,
194
+ `USER_CANCELLED`, `UNAUTHORIZED`, `ADDRESS_MISMATCH`, `STORAGE_UNAVAILABLE`, `LAST_DEVICE`,
195
+ `MESSAGE_TOO_LARGE`, `FRAME_OUTDATED`, `BACKUP_KEY_REQUIRED`, `ALREADY_UPGRADED`,
196
+ `ACCOUNT_UNAVAILABLE`, ...), reported unchanged by the Swift and Kotlin wallets. The JS layer adds
197
+ `NOT_SIGNED_IN` (no token), `INVALID_PARAMS`, `DISPOSED` and `NOT_CONFIGURED` (native module missing).
198
+ `USER_CANCELLED` means the user rejected the approval or closed the passkey sheet: go back without an
199
+ error message. `CONFIRM_WINDOW_BLOCKED` means the approval UI could not be shown; nothing was signed.
200
+
201
+ ```ts
202
+ try {
203
+ await wallet.signMessage(text);
204
+ } catch (e) {
205
+ if (e instanceof AuthonWalletError && e.code === 'USER_CANCELLED') return; // rejected in the approval UI
206
+ throw e;
207
+ }
208
+ ```
209
+
210
+ ## Trust model
211
+
212
+ The web wallet runs in a separate origin (wallet.authon.dev), so the page that embeds it can never
213
+ read the device share or the key. In an app SDK the wallet runs **inside your app's process**:
214
+
215
+ | | Web frame | App SDK (this package) |
216
+ |---|---|---|
217
+ | Where secrets live | Separate origin frame; the site's code cannot read D or k | Inside the app process; the app's own code could in principle reach D, A and k |
218
+ | Authon alone | Holds A only, cannot sign | Same |
219
+ | The app operator alone | Cannot sign | Can reach k at the moment the user signs in this app (the app is inside the trust boundary) |
220
+ | Revoking a device | Server stops serving A | Same; the key is never stored, so the server really blocks the device |
221
+
222
+ So the app SDK stays non-custodial with respect to Authon, but not with respect to the developer of
223
+ the app that embeds it. This is the common limit of every in-app embedded wallet SDK. The bridge keeps
224
+ secrets out of JS (JS bundles, logs, crash reporters and over-the-air updates never see them), but it
225
+ does not protect against native code in the same app.
226
+
227
+ ## Testing on simulators and emulators
228
+
229
+ Simulators and emulators have no Secure Enclave / StrongBox and no real passkey PRF. For automated
230
+ tests, pass `testing: { allowSoftwareKeys: true, autoApprove: true }` in a debug build only. Passkey
231
+ `protect()` / `restore()` cannot be exercised there; they are covered by the Swift and Kotlin SDKs'
232
+ tests (software WebAuthn test doubles and the live API tests) and need a real device for acceptance.
233
+ `examples/react-native-wallet` contains such a debug smoke app.
234
+
235
+ ## How the native code is shared
236
+
237
+ The module compiles the native SDKs' own sources; there is no separate wallet implementation:
238
+
239
+ - `ios/vendor/` is generated from `swift/Sources/AuthonWallet`, `swift/Sources/CSecp256k1` and
240
+ `swift/Sources/CKeccak`,
241
+ - `android/src/vendor/kotlin/` is generated from `kotlin/authon/.../dev/authon/sdk/wallet`
242
+ (without `AuthonWalletAuth.kt`, which needs the Kotlin auth client), plus `android/consumer-rules.pro`.
243
+
244
+ `node scripts/sync-native-wallet.mjs` (run by this package's `build`) regenerates them, `prepublishOnly` refuses to publish stale copies, and
245
+ `--check` (also part of the test suite) fails on any drift. Do not edit the generated files.
246
+
247
+ Note for apps that also include the native `dev.authon:authon-android` artifact: it contains the same
248
+ `dev.authon.sdk.wallet` classes, so use one or the other.
249
+
250
+ ## License
251
+
252
+ MIT
@@ -0,0 +1,40 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ }
5
+
6
+ group = 'dev.authon'
7
+ version = '0.1.0'
8
+
9
+ android {
10
+ namespace 'dev.authon.reactnative.wallet'
11
+ // compileSdk comes from the app (Expo 53+ uses 35 or later, which androidx.credentials 1.6.0 needs).
12
+ // The wallet needs API 28 (StrongBox, BiometricPrompt), so the app's minSdkVersion must be 28 or later.
13
+ defaultConfig {
14
+ minSdkVersion 28
15
+ versionCode 1
16
+ versionName '0.1.0'
17
+ consumerProguardFiles 'consumer-rules.pro'
18
+ }
19
+ // src/vendor/kotlin is generated from kotlin/authon (dev.authon.sdk.wallet) by scripts/sync-native-wallet.mjs.
20
+ sourceSets {
21
+ main.java.srcDirs += 'src/vendor/kotlin'
22
+ }
23
+ packagingOptions {
24
+ resources.excludes += ['META-INF/versions/9/OSGI-INF/MANIFEST.MF', 'META-INF/DEPENDENCIES']
25
+ }
26
+ }
27
+
28
+ // The dependency set of dev.authon.sdk.wallet (see kotlin/authon/build.gradle.kts).
29
+ dependencies {
30
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2'
31
+ implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1'
32
+ implementation 'com.squareup.okhttp3:okhttp:4.12.0'
33
+ implementation('org.web3j:crypto:4.14.0') {
34
+ exclude group: 'io.consensys.protocols', module: 'jc-kzg-4844'
35
+ exclude group: 'io.consensys.tuweni'
36
+ exclude group: 'com.fasterxml.jackson.core'
37
+ }
38
+ implementation 'androidx.credentials:credentials:1.6.0'
39
+ implementation 'androidx.credentials:credentials-play-services-auth:1.6.0'
40
+ }
@@ -0,0 +1,10 @@
1
+ # GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/consumer-rules.pro. Do not edit: change the source and run the script.
2
+ # web3j ships EIP-4844 blob support that needs KZG/tuweni; the Authon SDK never calls it.
3
+ -dontwarn ethereum.ckzg4844.**
4
+ -dontwarn org.apache.tuweni.**
5
+ -dontwarn org.slf4j.**
6
+ -dontwarn com.fasterxml.jackson.**
7
+ -keep class org.web3j.crypto.** { *; }
8
+ # Credential Manager with Play Services
9
+ -if class androidx.credentials.CredentialManager
10
+ -keep class androidx.credentials.playservices.** { *; }
@@ -0,0 +1,8 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
3
+ <uses-permission android:name="android.permission.USE_BIOMETRIC" />
4
+ <!-- capabilities(): checks whether Google Play services (passkey provider) is installed. -->
5
+ <queries>
6
+ <package android:name="com.google.android.gms" />
7
+ </queries>
8
+ </manifest>
@@ -0,0 +1,235 @@
1
+ package dev.authon.reactnative.wallet
2
+
3
+ import android.content.pm.ApplicationInfo
4
+ import dev.authon.sdk.wallet.AuthonWallet
5
+ import dev.authon.sdk.wallet.CapabilityCheck
6
+ import dev.authon.sdk.wallet.WalletCapabilities
7
+ import dev.authon.sdk.wallet.WalletChain
8
+ import dev.authon.sdk.wallet.PublicWallet
9
+ import dev.authon.sdk.wallet.RestoreMode
10
+ import dev.authon.sdk.wallet.SignAccount
11
+ import dev.authon.sdk.wallet.WalletApprover
12
+ import dev.authon.sdk.wallet.WalletAppConfig
13
+ import dev.authon.sdk.wallet.WalletErrorCode
14
+ import dev.authon.sdk.wallet.WalletException
15
+ import dev.authon.sdk.wallet.WalletMessage
16
+ import dev.authon.sdk.wallet.WalletOptions
17
+ import expo.modules.kotlin.functions.Coroutine
18
+ import expo.modules.kotlin.modules.Module
19
+ import expo.modules.kotlin.modules.ModuleDefinition
20
+ import java.util.concurrent.ConcurrentHashMap
21
+ import kotlinx.serialization.json.Json
22
+ import kotlinx.serialization.json.JsonArray
23
+ import kotlinx.serialization.json.JsonElement
24
+ import kotlinx.serialization.json.JsonNull
25
+ import kotlinx.serialization.json.JsonObject
26
+ import kotlinx.serialization.json.JsonPrimitive
27
+ import kotlinx.serialization.json.booleanOrNull
28
+ import kotlinx.serialization.json.doubleOrNull
29
+ import kotlinx.serialization.json.longOrNull
30
+ import kotlin.coroutines.AbstractCoroutineContextElement
31
+ import kotlin.coroutines.CoroutineContext
32
+ import kotlinx.coroutines.CancellationException
33
+ import kotlinx.coroutines.currentCoroutineContext
34
+ import kotlinx.coroutines.withContext
35
+
36
+ /**
37
+ * Bridges the AuthonWallet core (src/vendor/kotlin, generated from kotlin/authon) to JS.
38
+ *
39
+ * Only public data crosses the bridge: addresses, signatures, status and devices. The device share,
40
+ * the company share, the key and passkey PRF output stay inside the core. Every function resolves an
41
+ * envelope `{ ok: true, value }` or `{ ok: false, code, message }` with the web wallet's error codes.
42
+ */
43
+ class AuthonWalletModule : Module() {
44
+ private val wallets = ConcurrentHashMap<String, AuthonWallet>()
45
+
46
+ override fun definition() = ModuleDefinition {
47
+ Name("AuthonWallet")
48
+
49
+ AsyncFunction("configure") { id: String, config: Map<String, Any?> ->
50
+ envelope { configure(id, config); null }
51
+ }
52
+
53
+ AsyncFunction("release") { id: String ->
54
+ wallets.remove(id)
55
+ mapOf("ok" to true, "value" to null)
56
+ }
57
+
58
+ AsyncFunction("appConfig") Coroutine { id: String ->
59
+ call(id, null) { appConfig(it.appConfig()) }
60
+ }
61
+
62
+ AsyncFunction("capabilities") Coroutine { id: String ->
63
+ call(id, null) { capabilities(it.capabilities()) }
64
+ }
65
+
66
+ AsyncFunction("status") Coroutine { id: String, token: String ->
67
+ call(id, token) { wallet(it.status()) }
68
+ }
69
+
70
+ AsyncFunction("create") Coroutine { id: String, token: String ->
71
+ call(id, token) { wallet(it.create()) }
72
+ }
73
+
74
+ AsyncFunction("accounts") Coroutine { id: String, token: String, start: Int, count: Int, chain: String ->
75
+ call(id, token) { w ->
76
+ w.accounts(start.toLong(), count, WalletChain.fromWire(chain)).map {
77
+ mapOf("index" to it.index.toInt(), "path" to it.path, "address" to it.address, "chain" to it.chain.wire, "publicKey" to it.publicKey)
78
+ }
79
+ }
80
+ }
81
+
82
+ AsyncFunction("signMessage") Coroutine { id: String, token: String, kind: String, message: String, account: Int, address: String? ->
83
+ call(id, token) {
84
+ val payload = if (kind == "raw") WalletMessage.Raw(message) else WalletMessage.Text(message)
85
+ it.signMessage(payload, SignAccount.parse(account.toLong(), address))
86
+ }
87
+ }
88
+
89
+ AsyncFunction("signTypedData") Coroutine { id: String, token: String, typedDataJson: String, account: Int, address: String? ->
90
+ call(id, token) { it.signTypedData(typedDataJson, SignAccount.parse(account.toLong(), address)) }
91
+ }
92
+
93
+ AsyncFunction("signTransaction") Coroutine { id: String, token: String, transactionJson: String, account: Int, address: String? ->
94
+ call(id, token) { it.signTransaction(transactionJson, SignAccount.parse(account.toLong(), address)) }
95
+ }
96
+
97
+ AsyncFunction("signChainTransaction") Coroutine { id: String, token: String, chain: String, account: Int, address: String, payloadJson: String ->
98
+ call(id, token) { it.signChainTransaction(WalletChain.fromWire(chain), account.toLong(), address, parsePayload(payloadJson)) }
99
+ }
100
+
101
+ AsyncFunction("protect") Coroutine { id: String, token: String ->
102
+ call(id, token) { mapOf("shareVersion" to it.protect().shareVersion) }
103
+ }
104
+
105
+ AsyncFunction("upgradeBackup") Coroutine { id: String, token: String ->
106
+ call(id, token) { mapOf("shareVersion" to it.upgradeBackup().shareVersion) }
107
+ }
108
+
109
+ AsyncFunction("restore") Coroutine { id: String, token: String, reset: Boolean ->
110
+ call(id, token) {
111
+ val r = it.restore(if (reset) RestoreMode.RESET else RestoreMode.ADD)
112
+ mapOf("shareVersion" to r.shareVersion, "backupUpgradeRecommended" to r.backupUpgradeRecommended)
113
+ }
114
+ }
115
+
116
+ AsyncFunction("revokeDevice") Coroutine { id: String, token: String, deviceId: String ->
117
+ call(id, token) { it.revokeDevice(deviceId); null }
118
+ }
119
+ }
120
+
121
+ // Instances
122
+
123
+ private fun configure(id: String, config: Map<String, Any?>) {
124
+ val context = appContext.reactContext ?: throw WalletException(WalletErrorCode.INTERNAL, "no React context")
125
+ val publishableKey = (config["publishableKey"] as? String)?.takeIf { it.isNotEmpty() }
126
+ ?: throw WalletException(WalletErrorCode.INVALID_REQUEST, "publishableKey required")
127
+ val apiUrl = (config["apiUrl"] as? String)?.takeIf { it.isNotEmpty() } ?: AuthonWallet.DEFAULT_API_URL
128
+ val appId = (config["appId"] as? String)?.takeIf { it.isNotEmpty() } ?: context.packageName
129
+ val testing = config["testing"] as? Map<*, *> ?: emptyMap<String, Any?>()
130
+ val softwareKeys = testing["allowSoftwareKeys"] == true
131
+ val autoApprove = testing["autoApprove"] == true
132
+ val debuggable = (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
133
+ if ((softwareKeys || autoApprove) && !debuggable) {
134
+ throw WalletException(WalletErrorCode.INVALID_REQUEST, "testing options are only available in debug builds")
135
+ }
136
+ val activity = { appContext.currentActivity }
137
+ wallets[id] = AuthonWallet(
138
+ context = context.applicationContext,
139
+ publishableKey = publishableKey,
140
+ apiUrl = apiUrl,
141
+ appId = appId,
142
+ // The token is the Authon session token for this call, not a wallet secret.
143
+ sessionProvider = { currentCoroutineContext()[CallToken]?.token },
144
+ activityProvider = activity,
145
+ options = WalletOptions(
146
+ // Debug builds only (checked above): approves without UI for automated smoke tests.
147
+ approver = if (autoApprove) WalletApprover { true } else null,
148
+ requireDeviceAuthentication = config["requireDeviceAuthentication"] == true,
149
+ allowSoftwareKeysForTesting = softwareKeys,
150
+ ),
151
+ )
152
+ }
153
+
154
+ private suspend fun call(id: String, token: String?, body: suspend (AuthonWallet) -> Any?): Map<String, Any?> {
155
+ val wallet = wallets[id] ?: return failure(WalletException(WalletErrorCode.INVALID_REQUEST, "wallet not configured"))
156
+ return try {
157
+ val value = if (token == null) body(wallet) else withContext(CallToken(token)) { body(wallet) }
158
+ mapOf("ok" to true, "value" to value)
159
+ } catch (e: CancellationException) {
160
+ failure(WalletException(WalletErrorCode.USER_CANCELLED))
161
+ } catch (e: Throwable) {
162
+ failure(e)
163
+ }
164
+ }
165
+
166
+ private inline fun envelope(body: () -> Any?): Map<String, Any?> = try {
167
+ mapOf("ok" to true, "value" to body())
168
+ } catch (e: Throwable) {
169
+ failure(e)
170
+ }
171
+
172
+ private fun failure(e: Throwable): Map<String, Any?> {
173
+ val w = e as? WalletException ?: WalletException(WalletErrorCode.INTERNAL, "unexpected error")
174
+ return mapOf(
175
+ "ok" to false,
176
+ "code" to w.code.name,
177
+ "message" to (w.message ?: w.code.name),
178
+ "diagnosticId" to w.diagnosticId,
179
+ "retryable" to w.retryable,
180
+ )
181
+ }
182
+
183
+ private fun parsePayload(json: String): Map<String, Any?> {
184
+ val obj = runCatching { Json.parseToJsonElement(json) }.getOrNull() as? JsonObject
185
+ ?: throw WalletException(WalletErrorCode.INVALID_REQUEST, "payload must be a JSON object")
186
+ return obj.mapValues { (_, v) -> plain(v) }
187
+ }
188
+
189
+ private fun plain(e: JsonElement): Any? = when (e) {
190
+ is JsonNull -> null
191
+ is JsonObject -> e.mapValues { (_, v) -> plain(v) }
192
+ is JsonArray -> e.map { plain(it) }
193
+ is JsonPrimitive -> if (e.isString) e.content else e.booleanOrNull ?: e.longOrNull ?: e.doubleOrNull
194
+ }
195
+
196
+ // Public data only
197
+
198
+ private fun wallet(w: PublicWallet): Map<String, Any?> = mapOf(
199
+ "address" to w.address,
200
+ "protected" to w.protected,
201
+ "needsRestore" to w.needsRestore,
202
+ "backupUpgradeRecommended" to w.backupUpgradeRecommended,
203
+ "devices" to w.devices.map {
204
+ mapOf("id" to it.id, "label" to it.label, "createdAt" to it.createdAt, "lastUsedAt" to it.lastUsedAt, "current" to it.current)
205
+ },
206
+ )
207
+
208
+ private fun capabilities(c: WalletCapabilities): Map<String, Any?> {
209
+ fun check(k: CapabilityCheck) = mapOf("status" to k.status.wire, "reason" to k.reason)
210
+ return mapOf(
211
+ "ready" to c.ready.wire,
212
+ "app" to check(c.app),
213
+ "wallet" to check(c.wallet),
214
+ "secureStorage" to check(c.secureStorage),
215
+ "deviceLock" to check(c.deviceLock),
216
+ "passkeyBackup" to check(c.passkeyBackup),
217
+ "diagnosticId" to c.diagnosticId,
218
+ )
219
+ }
220
+
221
+ private fun appConfig(c: WalletAppConfig): Map<String, Any?> = mapOf(
222
+ "projectId" to c.projectId,
223
+ "allowed" to c.allowed,
224
+ "approved" to c.approved,
225
+ "enabled" to c.enabled,
226
+ "chainIds" to c.chainIds.map { it.toDouble() },
227
+ "confirmSigning" to c.confirmSigning,
228
+ "rpId" to c.rpId,
229
+ "minFrameProtocol" to c.minFrameProtocol,
230
+ )
231
+
232
+ private class CallToken(val token: String) : AbstractCoroutineContextElement(CallToken) {
233
+ companion object Key : CoroutineContext.Key<CallToken>
234
+ }
235
+ }
@@ -0,0 +1,4 @@
1
+ # Generated sources
2
+
3
+ Everything in this folder is copied from `kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet (without AuthonWalletAuth.kt)` by
4
+ `scripts/sync-native-wallet.mjs` (run by the package build). Do not edit these files; change the native SDK sources and run the script. `--check` fails on any difference.
@@ -0,0 +1,86 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/AndroidCapabilityProbe.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import android.app.KeyguardManager
5
+ import android.content.Context
6
+ import android.content.pm.PackageManager
7
+ import android.os.Build
8
+ import android.security.keystore.KeyGenParameterSpec
9
+ import android.security.keystore.KeyProperties
10
+ import kotlinx.coroutines.Dispatchers
11
+ import kotlinx.coroutines.withContext
12
+ import java.security.KeyPairGenerator
13
+ import java.security.KeyStore
14
+ import java.security.MessageDigest
15
+ import java.security.spec.ECGenParameterSpec
16
+ import java.util.UUID
17
+
18
+ /** Device checks for capabilities() on Android. Test-only software-key settings are ignored. */
19
+ class AndroidCapabilityProbe(context: Context) : CapabilityProbe {
20
+ private val appContext = context.applicationContext
21
+
22
+ override fun signingCertSha256(): String? {
23
+ if (Build.VERSION.SDK_INT < 28) return null
24
+ val info = appContext.packageManager.getPackageInfo(appContext.packageName, PackageManager.GET_SIGNING_CERTIFICATES)
25
+ val cert = info.signingInfo?.apkContentsSigners?.firstOrNull() ?: return null
26
+ return fingerprint(cert.toByteArray())
27
+ }
28
+
29
+ override suspend fun secureStorage(): CapabilityCheck = withContext(Dispatchers.IO) {
30
+ if (isEmulator()) return@withContext CapabilityCheck.unknown(Capabilities.SIMULATOR)
31
+ val alias = "dev.authon.wallet.probe.${UUID.randomUUID()}"
32
+ val ks = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
33
+ try {
34
+ val spec = KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_SIGN)
35
+ .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
36
+ .setDigests(KeyProperties.DIGEST_SHA256)
37
+ .build()
38
+ val pair = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore").apply { initialize(spec) }.generateKeyPair()
39
+ when (KeystoreSecurity.levelOf(pair.private)) {
40
+ KeySecurityLevel.STRONGBOX, KeySecurityLevel.TRUSTED_ENVIRONMENT, KeySecurityLevel.UNKNOWN_SECURE -> CapabilityCheck.AVAILABLE
41
+ KeySecurityLevel.SOFTWARE -> CapabilityCheck.unavailable(Capabilities.HARDWARE_KEYSTORE_UNAVAILABLE)
42
+ KeySecurityLevel.UNKNOWN -> CapabilityCheck.unknown(Capabilities.CHECK_FAILED)
43
+ }
44
+ } catch (_: Exception) {
45
+ CapabilityCheck.unknown(Capabilities.CHECK_FAILED)
46
+ } finally {
47
+ runCatching { ks.deleteEntry(alias) }
48
+ }
49
+ }
50
+
51
+ override fun deviceLock(): CapabilityCheck {
52
+ val km = appContext.getSystemService(KeyguardManager::class.java) ?: return CapabilityCheck.unknown(Capabilities.CHECK_FAILED)
53
+ return if (km.isDeviceSecure) CapabilityCheck.AVAILABLE else CapabilityCheck.unavailable(Capabilities.DEVICE_LOCK_NOT_SET)
54
+ }
55
+
56
+ override fun passkeyBackup(): CapabilityCheck {
57
+ if (Build.VERSION.SDK_INT < 28) return CapabilityCheck.unavailable(Capabilities.OS_UNSUPPORTED)
58
+ // Android 14+ ships the Credential Manager framework service; older versions need Play services.
59
+ val framework = Build.VERSION.SDK_INT >= 34 && appContext.getSystemService("credential") != null
60
+ val playServices = try {
61
+ appContext.packageManager.getApplicationInfo("com.google.android.gms", 0).enabled
62
+ } catch (_: PackageManager.NameNotFoundException) {
63
+ false
64
+ }
65
+ return if (framework || playServices) {
66
+ CapabilityCheck.unknown(Capabilities.PRF_PROVIDER_UNVERIFIED)
67
+ } else {
68
+ CapabilityCheck.unavailable(Capabilities.PASSKEY_API_UNAVAILABLE)
69
+ }
70
+ }
71
+
72
+ companion object {
73
+ fun fingerprint(der: ByteArray): String =
74
+ MessageDigest.getInstance("SHA-256").digest(der).joinToString(":") { "%02X".format(it.toInt() and 0xff) }
75
+
76
+ /** Conservative emulator detection (goldfish/ranchu hardware, generic/emulator builds). */
77
+ fun isEmulator(): Boolean {
78
+ val hw = Build.HARDWARE.lowercase()
79
+ val fp = Build.FINGERPRINT.lowercase()
80
+ val product = Build.PRODUCT.lowercase()
81
+ return hw.contains("goldfish") || hw.contains("ranchu") || fp.startsWith("generic") ||
82
+ fp.contains("emulator") || fp.contains("/sdk_gphone") || product.startsWith("sdk_gphone") ||
83
+ product.contains("emulator") || product.contains("simulator")
84
+ }
85
+ }
86
+ }