@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,218 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/AuthonWallet.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.content.Context
6
+ import kotlinx.coroutines.CancellationException
7
+ import kotlinx.coroutines.Dispatchers
8
+ import kotlinx.coroutines.sync.Mutex
9
+ import kotlinx.coroutines.sync.withLock
10
+ import kotlinx.coroutines.withContext
11
+ import kotlinx.serialization.json.Json
12
+ import kotlinx.serialization.json.JsonObject
13
+ import okhttp3.OkHttpClient
14
+ import kotlin.coroutines.CoroutineContext
15
+
16
+ data class WalletOptions(
17
+ /** Replaces the default approval dialog. The app is inside the trust boundary, so this is allowed. */
18
+ val approver: WalletApprover? = null,
19
+ /** Also require the device lock (BiometricPrompt) before every signature. */
20
+ val requireDeviceAuthentication: Boolean = false,
21
+ /** Replaces the Keystore device store (tests). */
22
+ val deviceStore: DeviceStore? = null,
23
+ /** Replaces the Credential Manager passkeys (tests). */
24
+ val passkeys: PasskeyProvider? = null,
25
+ /** TEST ONLY: accept software-backed Keystore keys (emulators). Never enable in production. */
26
+ val allowSoftwareKeysForTesting: Boolean = false,
27
+ )
28
+
29
+ /**
30
+ * Authon embedded wallet on Android. Same wallet row and `/v1/wallet/me/...` API (protocol 2) as the
31
+ * web frame, so web and app share one address per project user.
32
+ *
33
+ * The device stores only its share D. Every signature or accounts call fetches the company share A
34
+ * with a device-key proof, combines D+A in memory, derives the key, signs and wipes; the full key is
35
+ * never stored. All secrets stay inside this process (see README: trust model).
36
+ *
37
+ * Errors are [WalletException]s with the web frame's codes.
38
+ */
39
+ class AuthonWallet internal constructor(
40
+ private val api: WalletApi,
41
+ private val packageName: String,
42
+ private val sessionProvider: suspend () -> String?,
43
+ private val store: DeviceStore,
44
+ private val passkeysFactory: (rpId: String) -> PasskeyProvider,
45
+ private val approver: WalletApprover?,
46
+ private val deviceAuthenticator: DeviceAuthenticator?,
47
+ private val probe: CapabilityProbe?,
48
+ private val dispatcher: CoroutineContext = Dispatchers.Default,
49
+ ) {
50
+ /**
51
+ * Core constructor (no dependency on the Authon auth client, so wrappers such as the React
52
+ * Native module can reuse this package alone). [sessionProvider] returns the Authon access
53
+ * token; [appId] is the package name registered in the console.
54
+ */
55
+ @JvmOverloads
56
+ constructor(
57
+ context: Context,
58
+ publishableKey: String,
59
+ apiUrl: String = DEFAULT_API_URL,
60
+ appId: String = context.packageName,
61
+ sessionProvider: suspend () -> String?,
62
+ activityProvider: () -> Activity?,
63
+ options: WalletOptions = WalletOptions(),
64
+ httpClient: OkHttpClient? = null,
65
+ ) : this(
66
+ api = if (httpClient != null) WalletApi(apiUrl, publishableKey, httpClient) else WalletApi(apiUrl, publishableKey),
67
+ packageName = appId,
68
+ sessionProvider = sessionProvider,
69
+ store = options.deviceStore ?: KeystoreDeviceStore(context, options.allowSoftwareKeysForTesting),
70
+ passkeysFactory = { rpId -> options.passkeys ?: CredentialManagerPasskeys(activityProvider, rpId) },
71
+ approver = options.approver ?: DialogWalletApprover(activityProvider),
72
+ deviceAuthenticator = if (options.requireDeviceAuthentication) BiometricDeviceAuthenticator(activityProvider) else null,
73
+ probe = AndroidCapabilityProbe(context),
74
+ )
75
+
76
+ private val mutex = Mutex()
77
+ private var config: WalletAppConfig? = null
78
+
79
+ /** This app's wallet configuration from the API (cached after the first success). */
80
+ suspend fun appConfig(): WalletAppConfig = run { config ?: api.appConfig(packageName).also { config = it } }
81
+
82
+ /**
83
+ * Whether this device and app can use the wallet (NATIVE.md section 9). Needs no session,
84
+ * opens no login or passkey UI, creates nothing, and never throws.
85
+ */
86
+ suspend fun capabilities(): WalletCapabilities = withContext(dispatcher) {
87
+ val p = probe ?: throw IllegalStateException("no capability probe")
88
+ val (caps, cfg) = Capabilities.evaluate(api, packageName, p)
89
+ if (cfg != null) config = cfg
90
+ caps
91
+ }
92
+
93
+ private class Ready(val ops: WalletOps, val session: String, val config: WalletAppConfig)
94
+
95
+ private suspend fun ready(): Ready {
96
+ val cfg = appConfig()
97
+ val projectId = cfg.projectId
98
+ if (!cfg.allowed || projectId == null) throw walletError(WalletErrorCode.ORIGIN_NOT_ALLOWED, "this app is not registered for the Authon wallet")
99
+ if (!cfg.enabled) throw walletError(WalletErrorCode.ORIGIN_NOT_ALLOWED, "the wallet is disabled for this project")
100
+ if ((cfg.minFrameProtocol ?: 0) > WalletApi.FRAME_PROTOCOL) throw walletError(WalletErrorCode.FRAME_OUTDATED)
101
+ val session = sessionProvider()?.takeIf { it.isNotEmpty() } ?: throw walletError(WalletErrorCode.UNAUTHORIZED, "token required")
102
+ val ctx = WalletContext(
103
+ api = api,
104
+ store = store,
105
+ passkeys = passkeysFactory(cfg.rpId),
106
+ projectId = projectId,
107
+ confirmSigning = cfg.confirmSigning,
108
+ backupVersion = cfg.prfSaltVersion,
109
+ approver = approver,
110
+ deviceAuthenticator = deviceAuthenticator,
111
+ )
112
+ return Ready(WalletOps(ctx), session, cfg)
113
+ }
114
+
115
+ private fun requirePasskeyApproval(cfg: WalletAppConfig) {
116
+ if (!cfg.approved) {
117
+ throw walletError(WalletErrorCode.ORIGIN_NOT_ALLOWED, "this app is not yet approved for ${cfg.rpId} passkeys")
118
+ }
119
+ }
120
+
121
+ private suspend fun <T> op(block: suspend (Ready) -> T): T = withContext(dispatcher) {
122
+ mutex.withLock {
123
+ try {
124
+ block(ready())
125
+ } catch (e: CancellationException) {
126
+ throw e
127
+ } catch (e: Throwable) {
128
+ throw e.toWalletException()
129
+ }
130
+ }
131
+ }
132
+
133
+ /** The wallet; NOT_FOUND when this user has none yet. */
134
+ suspend fun status(): PublicWallet = op { it.ops.toPublicWallet(it.ops.status(it.session)) }
135
+
136
+ /** Creates the wallet (two-phase; resumes or discards an interrupted attempt), or returns the existing one. */
137
+ suspend fun create(): PublicWallet = op { it.ops.toPublicWallet(it.ops.createWallet(it.session)) }
138
+
139
+ /** Addresses of accounts start..start+count-1 on [chain] (NATIVE.md section 11). Signing is EVM only. */
140
+ suspend fun accounts(start: Long = 0, count: Int = 1, chain: WalletChain = WalletChain.EVM): List<WalletAccount> =
141
+ op { it.ops.accounts(it.session, start, count, chain) }
142
+
143
+ suspend fun signMessage(message: String, account: SignAccount = SignAccount.PRIMARY): String =
144
+ signMessage(WalletMessage.Text(message), account)
145
+
146
+ suspend fun signMessage(message: WalletMessage, account: SignAccount = SignAccount.PRIMARY): String =
147
+ op { it.ops.signMessage(it.session, message, account) }
148
+
149
+ /** personal_sign over raw bytes given as 0x hex. */
150
+ suspend fun signRawMessage(hex: String, account: SignAccount = SignAccount.PRIMARY): String =
151
+ signMessage(WalletMessage.Raw(hex), account)
152
+
153
+ /** EIP-712 v4 typed data as a JSON string. */
154
+ suspend fun signTypedData(typedDataJson: String, account: SignAccount = SignAccount.PRIMARY): String =
155
+ op { it.ops.signTypedData(it.session, parseObject(typedDataJson), account) }
156
+
157
+ /** EIP-712 v4 typed data as a map (`domain`, `types`, `primaryType`, `message`). */
158
+ suspend fun signTypedData(typedData: Map<String, Any?>, account: SignAccount = SignAccount.PRIMARY): String =
159
+ op { it.ops.signTypedData(it.session, JsValues.toJson(typedData) as JsonObject, account) }
160
+
161
+ /** Signs a legacy / EIP-2930 / EIP-1559 transaction given like the web frame's `tx` (numbers as numbers, decimal or hex strings). */
162
+ suspend fun signTransaction(transaction: Map<String, Any?>, account: SignAccount = SignAccount.PRIMARY): String =
163
+ op { it.ops.signTransaction(it.session, JsValues.toJson(transaction) as JsonObject, account) }
164
+
165
+ suspend fun signTransaction(transactionJson: String, account: SignAccount = SignAccount.PRIMARY): String =
166
+ op { it.ops.signTransaction(it.session, parseObject(transactionJson), account) }
167
+
168
+ /**
169
+ * Signs an app-built transaction on another chain (NATIVE.md section 12). Solana: payload
170
+ * `message` (serialized legacy/v0 Message, base64, at most 1232 bytes) and optional `cluster`
171
+ * (shown as "Solana <cluster>"); returns `signature` (base58). The signer must be the fee payer,
172
+ * and [address] must be the chain address of [account] (from accounts()). TRON: payload
173
+ * `rawDataHex` (raw_data protobuf, at most 4096 bytes); returns `txID` and `signature`
174
+ * (r||s||v hex); the owner must be the signer.
175
+ */
176
+ suspend fun signChainTransaction(chain: WalletChain, account: Long, address: String, payload: Map<String, Any?>): Map<String, String> =
177
+ op { it.ops.signChainTransaction(it.session, chain, account, address, payload) }
178
+
179
+ /** Creates the passkey backup (B) of an unprotected wallet. */
180
+ suspend fun protect(): ProtectResult = op {
181
+ requirePasskeyApproval(it.config)
182
+ it.ops.protect(it.session)
183
+ }
184
+
185
+ /** Replaces a legacy backup (no passkey public key) with a verifiable one, from a device that holds the wallet. */
186
+ suspend fun upgradeBackup(): ProtectResult = op {
187
+ requirePasskeyApproval(it.config)
188
+ it.ops.upgradeBackup(it.session)
189
+ }
190
+
191
+ /** Restores the wallet on this device with the backup passkey. */
192
+ suspend fun restore(mode: RestoreMode = RestoreMode.ADD): RestoreResult = op {
193
+ requirePasskeyApproval(it.config)
194
+ it.ops.restore(it.session, mode)
195
+ }
196
+
197
+ /** Revokes a device; signed with this device's key. */
198
+ suspend fun revokeDevice(deviceId: String): Boolean = op { it.ops.revokeDevice(it.session, deviceId) }
199
+
200
+ private fun parseObject(json: String): JsonObject =
201
+ runCatching { Json.parseToJsonElement(json) as? JsonObject }.getOrNull()
202
+ ?: throw walletError(WalletErrorCode.INVALID_REQUEST, "expected a JSON object")
203
+
204
+ companion object {
205
+ const val DEFAULT_API_URL = "https://api.authon.dev"
206
+
207
+ internal fun forTesting(
208
+ api: WalletApi,
209
+ packageName: String,
210
+ sessionProvider: suspend () -> String?,
211
+ store: DeviceStore,
212
+ passkeys: PasskeyProvider,
213
+ approver: WalletApprover?,
214
+ deviceAuthenticator: DeviceAuthenticator? = null,
215
+ probe: CapabilityProbe? = null,
216
+ ) = AuthonWallet(api, packageName, sessionProvider, store, { passkeys }, approver, deviceAuthenticator, probe)
217
+ }
218
+ }
@@ -0,0 +1,296 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Bitcoin.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import org.web3j.crypto.ECKeyPair
5
+ import org.web3j.crypto.Sign
6
+ import java.io.ByteArrayOutputStream
7
+ import java.math.BigInteger
8
+
9
+ /**
10
+ * Bitcoin PSBT signing (NATIVE.md section 12, `apps/wallet/src/bitcoin.ts`). Inputs whose
11
+ * witness_utxo is the account's P2WPKH script are signed (BIP143, SIGHASH_ALL, RFC 6979 low-S
12
+ * DER + 0x01); other inputs are left alone. The returned PSBT is the input PSBT with every
13
+ * key-value kept and our partial signatures added (key 0x02 || compressed public key).
14
+ */
15
+ internal object Bitcoin {
16
+ const val MAX_PSBT_BYTES = 100_000
17
+ private val MAGIC = byteArrayOf(0x70, 0x73, 0x62, 0x74, 0xff.toByte())
18
+
19
+ private fun invalid(why: String = "not a valid PSBT") = walletError(WalletErrorCode.INVALID_REQUEST, why)
20
+
21
+ class Input(val txid: ByteArray, val vout: ByteArray, val sequence: ByteArray)
22
+ class Output(val amount: Long, val script: ByteArray)
23
+ class KeyValue(val key: ByteArray, val value: ByteArray)
24
+
25
+ class Psbt(
26
+ val version: ByteArray,
27
+ val inputs: List<Input>,
28
+ val outputs: List<Output>,
29
+ val locktime: ByteArray,
30
+ val global: List<KeyValue>,
31
+ val inputMaps: List<MutableList<KeyValue>>,
32
+ val outputMaps: List<List<KeyValue>>,
33
+ ) {
34
+ fun witnessUtxo(i: Int): Output? = inputMaps[i].firstOrNull { it.key.size == 1 && it.key[0] == 0x01.toByte() }?.let {
35
+ val r = Reader(it.value)
36
+ val amount = r.u64()
37
+ val script = r.take(r.varint())
38
+ if (!r.done) throw invalid()
39
+ Output(amount, script)
40
+ }
41
+
42
+ fun sighashType(i: Int): Long? = inputMaps[i].firstOrNull { it.key.size == 1 && it.key[0] == 0x03.toByte() }?.let {
43
+ if (it.value.size != 4) throw invalid()
44
+ Reader(it.value).u32()
45
+ }
46
+ }
47
+
48
+ class Reader(private val b: ByteArray) {
49
+ var pos = 0
50
+ val done get() = pos == b.size
51
+
52
+ fun take(n: Long): ByteArray {
53
+ if (n < 0 || pos + n > b.size) throw invalid()
54
+ return b.copyOfRange(pos, pos + n.toInt()).also { pos += n.toInt() }
55
+ }
56
+
57
+ fun u8(): Int = take(1)[0].toInt() and 0xff
58
+
59
+ fun le(n: Int): Long {
60
+ val x = take(n.toLong())
61
+ var v = 0L
62
+ for (i in n - 1 downTo 0) v = (v shl 8) or (x[i].toLong() and 0xff)
63
+ return v
64
+ }
65
+
66
+ fun u32() = le(4)
67
+ fun u64() = le(8).also { if (it < 0) throw invalid() }
68
+
69
+ fun varint(): Long = when (val f = u8()) {
70
+ 0xfd -> le(2)
71
+ 0xfe -> le(4)
72
+ 0xff -> le(8).also { if (it < 0) throw invalid() }
73
+ else -> f.toLong()
74
+ }
75
+ }
76
+
77
+ private fun ByteArrayOutputStream.varint(n: Long) {
78
+ when {
79
+ n < 0xfd -> write(n.toInt())
80
+ n <= 0xffff -> { write(0xfd); le(n, 2) }
81
+ n <= 0xffffffffL -> { write(0xfe); le(n, 4) }
82
+ else -> { write(0xff); le(n, 8) }
83
+ }
84
+ }
85
+
86
+ private fun ByteArrayOutputStream.le(v: Long, n: Int) {
87
+ for (i in 0 until n) write(((v ushr (8 * i)) and 0xff).toInt())
88
+ }
89
+
90
+ private fun ByteArrayOutputStream.varBytes(b: ByteArray) {
91
+ varint(b.size.toLong())
92
+ write(b)
93
+ }
94
+
95
+ private fun readMap(r: Reader): MutableList<KeyValue> {
96
+ val out = ArrayList<KeyValue>()
97
+ while (true) {
98
+ val keyLen = r.varint()
99
+ if (keyLen == 0L) return out
100
+ val key = r.take(keyLen)
101
+ val value = r.take(r.varint())
102
+ if (out.any { it.key.contentEquals(key) }) throw invalid("duplicate PSBT key")
103
+ out += KeyValue(key, value)
104
+ }
105
+ }
106
+
107
+ fun parse(bytes: ByteArray): Psbt {
108
+ if (bytes.isEmpty() || bytes.size > MAX_PSBT_BYTES) throw invalid()
109
+ if (!bytes.copyOf(5).contentEquals(MAGIC)) throw invalid()
110
+ val r = Reader(bytes.copyOfRange(5, bytes.size))
111
+ val global = readMap(r)
112
+ val txField = global.filter { it.key.size == 1 && it.key[0] == 0x00.toByte() }
113
+ if (txField.size != 1) throw invalid("PSBT has no unsigned transaction")
114
+ val t = Reader(txField[0].value)
115
+ val version = t.take(4)
116
+ val inputs = List(t.varint().toInt().also { if (it < 1 || it > 10_000) throw invalid() }) {
117
+ val txid = t.take(32)
118
+ val vout = t.take(4)
119
+ if (t.varint() != 0L) throw invalid("unsigned transaction has a scriptSig")
120
+ Input(txid, vout, t.take(4))
121
+ }
122
+ val outputs = List(t.varint().toInt().also { if (it < 1 || it > 10_000) throw invalid() }) {
123
+ val amount = t.u64()
124
+ Output(amount, t.take(t.varint()))
125
+ }
126
+ val locktime = t.take(4)
127
+ if (!t.done) throw invalid()
128
+ val inputMaps = List(inputs.size) { readMap(r) }
129
+ val outputMaps = List(outputs.size) { readMap(r) }
130
+ if (!r.done) throw invalid()
131
+ return Psbt(version, inputs, outputs, locktime, global, inputMaps, outputMaps)
132
+ }
133
+
134
+ fun serialize(p: Psbt): ByteArray {
135
+ val out = ByteArrayOutputStream()
136
+ out.write(MAGIC)
137
+ for (map in listOf(p.global) + p.inputMaps + p.outputMaps) {
138
+ for (kv in map) {
139
+ out.varBytes(kv.key)
140
+ out.varBytes(kv.value)
141
+ }
142
+ out.write(0)
143
+ }
144
+ return out.toByteArray()
145
+ }
146
+
147
+ // ── Addresses ──
148
+
149
+ private class Net(val hrp: String, val p2pkh: Int, val p2sh: Int)
150
+
151
+ private fun net(chain: WalletChain) = if (chain == WalletChain.BITCOIN) Net("bc", 0x00, 0x05) else Net("tb", 0x6f, 0xc4)
152
+
153
+ fun scriptOf(address: String, chain: WalletChain): ByteArray {
154
+ val n = net(chain)
155
+ Bech32.decodeSegwit(n.hrp, address)?.let { (v, prog) ->
156
+ val op = if (v == 0) 0x00 else 0x50 + v
157
+ return byteArrayOf(op.toByte(), prog.size.toByte()) + prog
158
+ }
159
+ val raw = runCatching { Base58.decode(address) }.getOrNull()
160
+ if (raw != null && raw.size == 25 && Bytes.sha256(Bytes.sha256(raw.copyOf(21))).copyOf(4).contentEquals(raw.copyOfRange(21, 25))) {
161
+ val h = raw.copyOfRange(1, 21)
162
+ when (raw[0].toInt() and 0xff) {
163
+ n.p2pkh -> return byteArrayOf(0x76, 0xa9.toByte(), 0x14) + h + byteArrayOf(0x88.toByte(), 0xac.toByte())
164
+ n.p2sh -> return byteArrayOf(0xa9.toByte(), 0x14) + h + byteArrayOf(0x87.toByte())
165
+ }
166
+ }
167
+ throw walletError(WalletErrorCode.INVALID_REQUEST, "address is not a Bitcoin address of this network")
168
+ }
169
+
170
+ /** Address of a standard output script; null for scripts without one. */
171
+ fun addressOf(script: ByteArray, chain: WalletChain): String? {
172
+ val n = net(chain)
173
+ val s = script
174
+ fun u(i: Int) = s[i].toInt() and 0xff
175
+ return when {
176
+ s.size == 25 && u(0) == 0x76 && u(1) == 0xa9 && u(2) == 0x14 && u(23) == 0x88 && u(24) == 0xac ->
177
+ Base58.encodeCheck(byteArrayOf(n.p2pkh.toByte()) + s.copyOfRange(3, 23))
178
+ s.size == 23 && u(0) == 0xa9 && u(1) == 0x14 && u(22) == 0x87 ->
179
+ Base58.encodeCheck(byteArrayOf(n.p2sh.toByte()) + s.copyOfRange(2, 22))
180
+ s.size == 22 && u(0) == 0x00 && u(1) == 0x14 -> Bech32.segwit(n.hrp, 0, s.copyOfRange(2, 22))
181
+ s.size == 34 && u(0) == 0x00 && u(1) == 0x20 -> Bech32.segwit(n.hrp, 0, s.copyOfRange(2, 34))
182
+ s.size == 34 && u(0) == 0x51 && u(1) == 0x20 -> Bech32.segwit(n.hrp, 1, s.copyOfRange(2, 34))
183
+ else -> null
184
+ }
185
+ }
186
+
187
+ private fun btc(sats: BigInteger): String {
188
+ val neg = sats.signum() < 0
189
+ val (whole, rem) = sats.abs().divideAndRemainder(BigInteger.valueOf(100_000_000))
190
+ val frac = rem.toString().padStart(8, '0').trimEnd('0')
191
+ return "${if (neg) "-" else ""}$whole${if (frac.isEmpty()) "" else ".$frac"} BTC"
192
+ }
193
+
194
+ private fun ownInputs(p: Psbt, script: ByteArray): List<Int> =
195
+ p.inputs.indices.filter { p.witnessUtxo(it)?.script?.contentEquals(script) == true }
196
+
197
+ fun summarize(p: Psbt, signer: String, chain: WalletChain): SignSummary {
198
+ val script = scriptOf(signer, chain)
199
+ val rows = mutableListOf(
200
+ SignRow("Network", if (chain == WalletChain.BITCOIN) "Bitcoin" else "Bitcoin testnet"),
201
+ SignRow("Account", signer, mono = true),
202
+ )
203
+ val warnings = mutableListOf<String>()
204
+ var inputs = BigInteger.ZERO
205
+ for (i in p.inputs.indices) {
206
+ val utxo = p.witnessUtxo(i) ?: throw invalid("every input needs witness_utxo")
207
+ inputs = inputs.add(BigInteger.valueOf(utxo.amount))
208
+ }
209
+ val mine = ownInputs(p, script)
210
+ if (mine.isEmpty()) throw walletError(WalletErrorCode.ADDRESS_MISMATCH, "no input belongs to the signing account")
211
+ for (i in mine) {
212
+ val type = p.sighashType(i)
213
+ if (type != null && type != 1L) throw invalid("only SIGHASH_ALL is signed")
214
+ }
215
+ if (mine.size != p.inputs.size) warnings += "${p.inputs.size - mine.size} of ${p.inputs.size} inputs are not yours and are not signed."
216
+ rows += SignRow("Spending", btc(inputs))
217
+ var outputs = BigInteger.ZERO
218
+ for (o in p.outputs) {
219
+ outputs = outputs.add(BigInteger.valueOf(o.amount))
220
+ val to = addressOf(o.script, chain) ?: "non-standard output".also {
221
+ warnings += "An output has a script this wallet cannot show as an address."
222
+ }
223
+ rows += if (o.script.contentEquals(script)) {
224
+ SignRow("Change back to you", btc(BigInteger.valueOf(o.amount)))
225
+ } else {
226
+ SignRow("Send ${btc(BigInteger.valueOf(o.amount))} to", to, mono = true)
227
+ }
228
+ }
229
+ val fee = inputs.subtract(outputs)
230
+ if (fee.signum() < 0) throw invalid("outputs exceed inputs")
231
+ rows += SignRow("Network fee", btc(fee))
232
+ if (fee.multiply(BigInteger.TEN) > inputs && fee > BigInteger.valueOf(100_000)) {
233
+ warnings += "The network fee is more than 10% of the amount spent."
234
+ }
235
+ return SignSummary(SignSummary.Kind.TRANSACTION, "Bitcoin transaction", rows, null, warnings, SignRisk.HIGH)
236
+ }
237
+
238
+ private fun dsha(b: ByteArray) = Bytes.sha256(Bytes.sha256(b))
239
+
240
+ private fun outputBytes(o: Output) = ByteArrayOutputStream().apply { le(o.amount, 8); varBytes(o.script) }.toByteArray()
241
+
242
+ /** BIP143 sighash of input [i] for a P2WPKH output with [pubkeyHash], SIGHASH_ALL. */
243
+ fun sighash(p: Psbt, i: Int, pubkeyHash: ByteArray, amount: Long): ByteArray {
244
+ val prevouts = ByteArrayOutputStream().apply { for (x in p.inputs) { write(x.txid); write(x.vout) } }.toByteArray()
245
+ val sequences = ByteArrayOutputStream().apply { for (x in p.inputs) write(x.sequence) }.toByteArray()
246
+ val outs = ByteArrayOutputStream().apply { for (o in p.outputs) write(outputBytes(o)) }.toByteArray()
247
+ val pre = ByteArrayOutputStream()
248
+ pre.write(p.version)
249
+ pre.write(dsha(prevouts))
250
+ pre.write(dsha(sequences))
251
+ pre.write(p.inputs[i].txid)
252
+ pre.write(p.inputs[i].vout)
253
+ pre.write(byteArrayOf(0x19, 0x76, 0xa9.toByte(), 0x14) + pubkeyHash + byteArrayOf(0x88.toByte(), 0xac.toByte()))
254
+ pre.le(amount, 8)
255
+ pre.write(p.inputs[i].sequence)
256
+ pre.write(dsha(outs))
257
+ pre.write(p.locktime)
258
+ pre.le(1, 4)
259
+ return dsha(pre.toByteArray())
260
+ }
261
+
262
+ /** Signs our inputs; `txHex` is the finalized transaction when every input is ours. */
263
+ fun sign(p: Psbt, privateKey: ByteArray, chain: WalletChain): Map<String, String> {
264
+ val pub = Chains.compressedPublicKey(privateKey)
265
+ val hash = Chains.hash160Of(pub)
266
+ val script = byteArrayOf(0x00, 0x14) + hash
267
+ val mine = ownInputs(p, script)
268
+ if (mine.isEmpty()) throw walletError(WalletErrorCode.ADDRESS_MISMATCH)
269
+ val keyPair = ECKeyPair.create(privateKey)
270
+ val sigs = HashMap<Int, ByteArray>()
271
+ for (i in mine) {
272
+ val sig = Sign.signMessage(sighash(p, i, hash, p.witnessUtxo(i)!!.amount), keyPair, false)
273
+ val der = Der.encode(sig.r, sig.s) + byteArrayOf(0x01)
274
+ sigs[i] = der
275
+ val key = byteArrayOf(0x02) + pub
276
+ p.inputMaps[i].removeAll { it.key.contentEquals(key) }
277
+ p.inputMaps[i] += KeyValue(key, der)
278
+ }
279
+ val psbt = Bytes.toBase64(serialize(p))
280
+ if (mine.size != p.inputs.size) return mapOf("psbt" to psbt)
281
+ val tx = ByteArrayOutputStream()
282
+ tx.write(p.version)
283
+ tx.write(byteArrayOf(0x00, 0x01))
284
+ tx.varint(p.inputs.size.toLong())
285
+ for (x in p.inputs) { tx.write(x.txid); tx.write(x.vout); tx.write(0); tx.write(x.sequence) }
286
+ tx.varint(p.outputs.size.toLong())
287
+ for (o in p.outputs) tx.write(outputBytes(o))
288
+ for (i in p.inputs.indices) {
289
+ tx.write(2)
290
+ tx.varBytes(sigs.getValue(i))
291
+ tx.varBytes(pub)
292
+ }
293
+ tx.write(p.locktime)
294
+ return mapOf("psbt" to psbt, "txHex" to Bytes.toHex(tx.toByteArray()))
295
+ }
296
+ }
@@ -0,0 +1,74 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Bytes.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import java.security.MessageDigest
5
+ import java.security.SecureRandom
6
+ import java.util.Base64
7
+
8
+ internal object Bytes {
9
+ private val random = SecureRandom()
10
+
11
+ fun utf8(value: String): ByteArray = value.toByteArray(Charsets.UTF_8)
12
+
13
+ fun toBase64(bytes: ByteArray): String = Base64.getEncoder().encodeToString(bytes)
14
+
15
+ /** Accepts standard and URL-safe alphabets, with or without padding (like the frame's atob path). */
16
+ fun fromBase64(value: String): ByteArray {
17
+ val normalized = value.replace('-', '+').replace('_', '/').trimEnd('=')
18
+ return try {
19
+ Base64.getDecoder().decode(normalized.padEnd((normalized.length + 3) / 4 * 4, '='))
20
+ } catch (e: IllegalArgumentException) {
21
+ throw WalletException(WalletErrorCode.INVALID_REQUEST, "invalid base64", e)
22
+ }
23
+ }
24
+
25
+ fun toBase64Url(bytes: ByteArray): String = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)
26
+
27
+ fun toHex(bytes: ByteArray): String {
28
+ val out = StringBuilder(bytes.size * 2)
29
+ for (b in bytes) {
30
+ val v = b.toInt() and 0xff
31
+ out.append(HEX[v ushr 4]).append(HEX[v and 0x0f])
32
+ }
33
+ return out.toString()
34
+ }
35
+
36
+ /** Hex with or without 0x; null when malformed. */
37
+ fun fromHexOrNull(value: String): ByteArray? {
38
+ val body = if (value.startsWith("0x") || value.startsWith("0X")) value.substring(2) else value
39
+ if (body.length % 2 != 0) return null
40
+ val out = ByteArray(body.length / 2)
41
+ for (i in out.indices) {
42
+ val hi = Character.digit(body[i * 2], 16)
43
+ val lo = Character.digit(body[i * 2 + 1], 16)
44
+ if (hi < 0 || lo < 0) return null
45
+ out[i] = ((hi shl 4) or lo).toByte()
46
+ }
47
+ return out
48
+ }
49
+
50
+ fun fromHex(value: String): ByteArray =
51
+ fromHexOrNull(value) ?: throw WalletException(WalletErrorCode.INVALID_REQUEST, "invalid hex")
52
+
53
+ fun concat(vararg parts: ByteArray): ByteArray {
54
+ val out = ByteArray(parts.sumOf { it.size })
55
+ var offset = 0
56
+ for (p in parts) {
57
+ p.copyInto(out, offset)
58
+ offset += p.size
59
+ }
60
+ return out
61
+ }
62
+
63
+ fun randomBytes(length: Int): ByteArray = ByteArray(length).also { random.nextBytes(it) }
64
+
65
+ fun wipe(vararg buffers: ByteArray?) {
66
+ for (b in buffers) b?.fill(0)
67
+ }
68
+
69
+ fun sha256(data: ByteArray): ByteArray = MessageDigest.getInstance("SHA-256").digest(data)
70
+
71
+ fun sameBytes(a: ByteArray, b: ByteArray): Boolean = MessageDigest.isEqual(a, b)
72
+
73
+ private const val HEX = "0123456789abcdef"
74
+ }