@authon/react-native-wallet 0.0.0-stage → 0.1.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 (159) hide show
  1. package/README.md +221 -2
  2. package/android/build.gradle +40 -0
  3. package/android/consumer-rules.pro +10 -0
  4. package/android/src/main/AndroidManifest.xml +4 -0
  5. package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +181 -0
  6. package/android/src/vendor/GENERATED.md +4 -0
  7. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +191 -0
  8. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
  9. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
  10. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DeviceStore.kt +170 -0
  11. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt +141 -0
  12. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Eip712.kt +176 -0
  13. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmAccount.kt +45 -0
  14. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt +180 -0
  15. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/GfTables.kt +44 -0
  16. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/HdWallet.kt +130 -0
  17. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +82 -0
  18. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/JsValues.kt +83 -0
  19. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +232 -0
  20. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Prf.kt +111 -0
  21. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Proof.kt +134 -0
  22. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Shamir.kt +106 -0
  23. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/SignSummary.kt +335 -0
  24. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +353 -0
  25. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +58 -0
  26. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +449 -0
  27. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +71 -0
  28. package/build/authonContext.d.ts +19 -0
  29. package/build/authonContext.d.ts.map +1 -0
  30. package/build/authonContext.js +20 -0
  31. package/build/authonContext.js.map +1 -0
  32. package/build/client.d.ts +76 -0
  33. package/build/client.d.ts.map +1 -0
  34. package/build/client.js +349 -0
  35. package/build/client.js.map +1 -0
  36. package/build/errors.d.ts +18 -0
  37. package/build/errors.d.ts.map +1 -0
  38. package/build/errors.js +9 -0
  39. package/build/errors.js.map +1 -0
  40. package/build/index.d.ts +7 -0
  41. package/build/index.d.ts.map +1 -0
  42. package/build/index.js +4 -0
  43. package/build/index.js.map +1 -0
  44. package/build/native.d.ts +39 -0
  45. package/build/native.d.ts.map +1 -0
  46. package/build/native.js +12 -0
  47. package/build/native.js.map +1 -0
  48. package/build/types.d.ts +121 -0
  49. package/build/types.d.ts.map +1 -0
  50. package/build/types.js +3 -0
  51. package/build/types.js.map +1 -0
  52. package/build/useAuthonWallet.d.ts +25 -0
  53. package/build/useAuthonWallet.d.ts.map +1 -0
  54. package/build/useAuthonWallet.js +77 -0
  55. package/build/useAuthonWallet.js.map +1 -0
  56. package/expo-module.config.json +9 -0
  57. package/ios/AuthonReactNativeWallet.podspec +37 -0
  58. package/ios/AuthonWalletModule.swift +190 -0
  59. package/ios/vendor/AuthonWallet/AuthonWallet.swift +212 -0
  60. package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
  61. package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
  62. package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +104 -0
  63. package/ios/vendor/AuthonWallet/Crypto/Shamir.swift +123 -0
  64. package/ios/vendor/AuthonWallet/EVM/Address.swift +57 -0
  65. package/ios/vendor/AuthonWallet/EVM/EvmAccount.swift +48 -0
  66. package/ios/vendor/AuthonWallet/EVM/Transaction.swift +165 -0
  67. package/ios/vendor/AuthonWallet/EVM/TypedData.swift +193 -0
  68. package/ios/vendor/AuthonWallet/Support/BigInteger.swift +220 -0
  69. package/ios/vendor/AuthonWallet/Support/Bytes.swift +89 -0
  70. package/ios/vendor/AuthonWallet/Support/JSONValue.swift +341 -0
  71. package/ios/vendor/AuthonWallet/UI/WalletApprover.swift +250 -0
  72. package/ios/vendor/AuthonWallet/Wallet/Backup.swift +69 -0
  73. package/ios/vendor/AuthonWallet/Wallet/DeviceStore.swift +351 -0
  74. package/ios/vendor/AuthonWallet/Wallet/Passkeys.swift +190 -0
  75. package/ios/vendor/AuthonWallet/Wallet/Proof.swift +143 -0
  76. package/ios/vendor/AuthonWallet/Wallet/Shares.swift +116 -0
  77. package/ios/vendor/AuthonWallet/Wallet/Summary.swift +342 -0
  78. package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +350 -0
  79. package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +555 -0
  80. package/ios/vendor/AuthonWallet/WalletError.swift +54 -0
  81. package/ios/vendor/CKeccak/LICENSE +22 -0
  82. package/ios/vendor/CKeccak/README.md +27 -0
  83. package/ios/vendor/CKeccak/byte_order.h +59 -0
  84. package/ios/vendor/CKeccak/include/ckeccak.h +9 -0
  85. package/ios/vendor/CKeccak/memzero.c +75 -0
  86. package/ios/vendor/CKeccak/memzero.h +8 -0
  87. package/ios/vendor/CKeccak/options.h +99 -0
  88. package/ios/vendor/CKeccak/sha3.c +413 -0
  89. package/ios/vendor/CKeccak/sha3.h +89 -0
  90. package/ios/vendor/CSecp256k1/COPYING +19 -0
  91. package/ios/vendor/CSecp256k1/README.md +35 -0
  92. package/ios/vendor/CSecp256k1/build/precomputed_ecmult.c +3 -0
  93. package/ios/vendor/CSecp256k1/build/precomputed_ecmult_gen.c +3 -0
  94. package/ios/vendor/CSecp256k1/build/secp256k1.c +3 -0
  95. package/ios/vendor/CSecp256k1/include/secp256k1.h +899 -0
  96. package/ios/vendor/CSecp256k1/include/secp256k1_preallocated.h +134 -0
  97. package/ios/vendor/CSecp256k1/include/secp256k1_recovery.h +113 -0
  98. package/ios/vendor/CSecp256k1/src/assumptions.h +87 -0
  99. package/ios/vendor/CSecp256k1/src/checkmem.h +102 -0
  100. package/ios/vendor/CSecp256k1/src/ecdsa.h +21 -0
  101. package/ios/vendor/CSecp256k1/src/ecdsa_impl.h +304 -0
  102. package/ios/vendor/CSecp256k1/src/eckey.h +25 -0
  103. package/ios/vendor/CSecp256k1/src/eckey_impl.h +92 -0
  104. package/ios/vendor/CSecp256k1/src/ecmult.h +61 -0
  105. package/ios/vendor/CSecp256k1/src/ecmult_const.h +38 -0
  106. package/ios/vendor/CSecp256k1/src/ecmult_const_impl.h +399 -0
  107. package/ios/vendor/CSecp256k1/src/ecmult_gen.h +143 -0
  108. package/ios/vendor/CSecp256k1/src/ecmult_gen_impl.h +341 -0
  109. package/ios/vendor/CSecp256k1/src/ecmult_impl.h +853 -0
  110. package/ios/vendor/CSecp256k1/src/field.h +350 -0
  111. package/ios/vendor/CSecp256k1/src/field_10x26.h +57 -0
  112. package/ios/vendor/CSecp256k1/src/field_10x26_impl.h +1232 -0
  113. package/ios/vendor/CSecp256k1/src/field_5x52.h +62 -0
  114. package/ios/vendor/CSecp256k1/src/field_5x52_impl.h +522 -0
  115. package/ios/vendor/CSecp256k1/src/field_5x52_int128_impl.h +274 -0
  116. package/ios/vendor/CSecp256k1/src/field_impl.h +457 -0
  117. package/ios/vendor/CSecp256k1/src/group.h +212 -0
  118. package/ios/vendor/CSecp256k1/src/group_impl.h +974 -0
  119. package/ios/vendor/CSecp256k1/src/hash.h +44 -0
  120. package/ios/vendor/CSecp256k1/src/hash_impl.h +299 -0
  121. package/ios/vendor/CSecp256k1/src/hsort.h +33 -0
  122. package/ios/vendor/CSecp256k1/src/hsort_impl.h +125 -0
  123. package/ios/vendor/CSecp256k1/src/int128.h +90 -0
  124. package/ios/vendor/CSecp256k1/src/int128_impl.h +18 -0
  125. package/ios/vendor/CSecp256k1/src/int128_native.h +19 -0
  126. package/ios/vendor/CSecp256k1/src/int128_native_impl.h +94 -0
  127. package/ios/vendor/CSecp256k1/src/int128_struct.h +14 -0
  128. package/ios/vendor/CSecp256k1/src/int128_struct_impl.h +205 -0
  129. package/ios/vendor/CSecp256k1/src/modinv32.h +43 -0
  130. package/ios/vendor/CSecp256k1/src/modinv32_impl.h +725 -0
  131. package/ios/vendor/CSecp256k1/src/modinv64.h +47 -0
  132. package/ios/vendor/CSecp256k1/src/modinv64_impl.h +780 -0
  133. package/ios/vendor/CSecp256k1/src/modules/recovery/main_impl.h +159 -0
  134. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.c +16456 -0
  135. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.h +38 -0
  136. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.c +1779 -0
  137. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.h +26 -0
  138. package/ios/vendor/CSecp256k1/src/scalar.h +105 -0
  139. package/ios/vendor/CSecp256k1/src/scalar_4x64.h +19 -0
  140. package/ios/vendor/CSecp256k1/src/scalar_4x64_impl.h +1000 -0
  141. package/ios/vendor/CSecp256k1/src/scalar_8x32.h +19 -0
  142. package/ios/vendor/CSecp256k1/src/scalar_8x32_impl.h +816 -0
  143. package/ios/vendor/CSecp256k1/src/scalar_impl.h +321 -0
  144. package/ios/vendor/CSecp256k1/src/scalar_low.h +24 -0
  145. package/ios/vendor/CSecp256k1/src/scalar_low_impl.h +206 -0
  146. package/ios/vendor/CSecp256k1/src/scratch.h +44 -0
  147. package/ios/vendor/CSecp256k1/src/scratch_impl.h +99 -0
  148. package/ios/vendor/CSecp256k1/src/secp256k1.c +831 -0
  149. package/ios/vendor/CSecp256k1/src/selftest.h +32 -0
  150. package/ios/vendor/CSecp256k1/src/util.h +451 -0
  151. package/ios/vendor/GENERATED.md +4 -0
  152. package/package.json +65 -3
  153. package/src/authonContext.ts +30 -0
  154. package/src/client.ts +375 -0
  155. package/src/errors.ts +52 -0
  156. package/src/index.ts +21 -0
  157. package/src/native.ts +50 -0
  158. package/src/types.ts +120 -0
  159. package/src/useAuthonWallet.ts +103 -0
@@ -0,0 +1,176 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Eip712.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import kotlinx.serialization.json.JsonArray
5
+ import kotlinx.serialization.json.JsonElement
6
+ import kotlinx.serialization.json.JsonObject
7
+ import kotlinx.serialization.json.JsonPrimitive
8
+ import kotlinx.serialization.json.booleanOrNull
9
+ import kotlinx.serialization.json.contentOrNull
10
+ import org.web3j.crypto.Hash
11
+ import org.web3j.crypto.Keys
12
+ import java.math.BigInteger
13
+
14
+ /**
15
+ * EIP-712 v4 hashing with viem's rules (hashTypedData): the EIP712Domain type is derived from the
16
+ * domain fields present unless the request supplies one, dependencies are sorted, arrays hash the
17
+ * concatenated element encodings, and signed integers are 256-bit two's complement. Keccak comes
18
+ * from web3j; only the encoding is assembled here.
19
+ */
20
+ internal object Eip712 {
21
+ data class Field(val name: String, val type: String)
22
+
23
+ private val INT_TYPE = Regex("^(u?)int([0-9]*)$")
24
+ private val BYTES_N = Regex("^bytes([0-9]+)$")
25
+ private val ADDRESS = Regex("^0x[0-9a-fA-F]{40}$")
26
+ private val TWO_256: BigInteger = BigInteger.ONE.shiftLeft(256)
27
+
28
+ fun hashTypedData(typedData: JsonObject): ByteArray {
29
+ val domain = typedData["domain"] as? JsonObject ?: JsonObject(emptyMap())
30
+ val primaryType = (typedData["primaryType"] as? JsonPrimitive)?.contentOrNull
31
+ ?: throw invalid("primaryType missing")
32
+ val provided = parseTypes(typedData["types"] as? JsonObject ?: throw invalid("types missing"))
33
+ val types = LinkedHashMap<String, List<Field>>()
34
+ types["EIP712Domain"] = domainFields(domain)
35
+ types.putAll(provided)
36
+ val parts = mutableListOf(byteArrayOf(0x19, 0x01), hashStruct("EIP712Domain", domain, types))
37
+ if (primaryType != "EIP712Domain") {
38
+ val message = typedData["message"] as? JsonObject ?: throw invalid("message missing")
39
+ parts += hashStruct(primaryType, message, types)
40
+ }
41
+ return Hash.sha3(Bytes.concat(*parts.toTypedArray()))
42
+ }
43
+
44
+ fun parseTypes(obj: JsonObject): Map<String, List<Field>> = obj.mapValues { (name, fields) ->
45
+ (fields as? JsonArray ?: throw invalid("type $name must be an array")).map { f ->
46
+ val o = f as? JsonObject ?: throw invalid("field of $name must be an object")
47
+ Field(
48
+ (o["name"] as? JsonPrimitive)?.contentOrNull ?: throw invalid("field name missing in $name"),
49
+ (o["type"] as? JsonPrimitive)?.contentOrNull ?: throw invalid("field type missing in $name"),
50
+ )
51
+ }
52
+ }
53
+
54
+ /** viem getTypesForEIP712Domain. */
55
+ private fun domainFields(domain: JsonObject): List<Field> = buildList {
56
+ val name = domain["name"] as? JsonPrimitive
57
+ if (name != null && name.isString) add(Field("name", "string"))
58
+ val version = domain["version"] as? JsonPrimitive
59
+ if (version != null && truthy(version)) add(Field("version", "string"))
60
+ val chainId = domain["chainId"] as? JsonPrimitive
61
+ if (chainId != null && !chainId.isString && chainId.booleanOrNull == null && chainId.contentOrNull != null) {
62
+ add(Field("chainId", "uint256"))
63
+ }
64
+ val vc = domain["verifyingContract"] as? JsonPrimitive
65
+ if (vc != null && truthy(vc)) add(Field("verifyingContract", "address"))
66
+ val salt = domain["salt"] as? JsonPrimitive
67
+ if (salt != null && truthy(salt)) add(Field("salt", "bytes32"))
68
+ }
69
+
70
+ private fun truthy(p: JsonPrimitive): Boolean {
71
+ val c = p.contentOrNull ?: return false
72
+ if (p.isString) return c.isNotEmpty()
73
+ p.booleanOrNull?.let { return it }
74
+ return c.toBigDecimalOrNull()?.signum() != 0
75
+ }
76
+
77
+ fun encodeType(primaryType: String, types: Map<String, List<Field>>): String {
78
+ val deps = LinkedHashSet<String>()
79
+ findDependencies(primaryType, types, deps)
80
+ val ordered = listOf(primaryType) + deps.filter { it != primaryType }.sorted()
81
+ return ordered.joinToString("") { t ->
82
+ "$t(" + types.getValue(t).joinToString(",") { "${it.type} ${it.name}" } + ")"
83
+ }
84
+ }
85
+
86
+ private fun findDependencies(type: String, types: Map<String, List<Field>>, out: MutableSet<String>) {
87
+ val base = Regex("^\\w*").find(type)?.value ?: ""
88
+ if (base in out || types[base] == null) return
89
+ out += base
90
+ for (f in types.getValue(base)) findDependencies(f.type, types, out)
91
+ }
92
+
93
+ fun hashStruct(type: String, data: JsonObject, types: Map<String, List<Field>>): ByteArray {
94
+ val fields = types[type] ?: throw invalid("unknown type $type")
95
+ val parts = ArrayList<ByteArray>(fields.size + 1)
96
+ parts += Hash.sha3(Bytes.utf8(encodeType(type, types)))
97
+ for (f in fields) parts += encodeField(f.type, data[f.name], types)
98
+ return Hash.sha3(Bytes.concat(*parts.toTypedArray()))
99
+ }
100
+
101
+ private fun encodeField(type: String, value: JsonElement?, types: Map<String, List<Field>>): ByteArray {
102
+ if (types[type] != null) {
103
+ val obj = value as? JsonObject ?: throw invalid("$type value must be an object")
104
+ return hashStruct(type, obj, types)
105
+ }
106
+ if (type.endsWith("]")) {
107
+ val open = type.lastIndexOf('[')
108
+ val inner = type.substring(0, open)
109
+ val len = type.substring(open + 1, type.length - 1)
110
+ val arr = value as? JsonArray ?: throw invalid("$type value must be an array")
111
+ if (len.isNotEmpty() && len.toIntOrNull() != arr.size) throw invalid("$type expects $len elements")
112
+ return Hash.sha3(Bytes.concat(*arr.map { encodeField(inner, it, types) }.toTypedArray()))
113
+ }
114
+ val p = value as? JsonPrimitive ?: throw invalid("missing value for $type")
115
+ return when {
116
+ type == "string" -> {
117
+ if (!p.isString) throw invalid("string value expected")
118
+ Hash.sha3(Bytes.utf8(p.content))
119
+ }
120
+ type == "bytes" -> {
121
+ if (!p.isString) throw invalid("hex value expected")
122
+ Hash.sha3(Bytes.fromHexOrNull(p.content)?.takeIf { p.content.startsWith("0x") } ?: throw invalid("bytes must be 0x hex"))
123
+ }
124
+ type == "bool" -> {
125
+ val b = if (p.isString) null else p.booleanOrNull
126
+ word(if (b ?: throw invalid("boolean value expected")) BigInteger.ONE else BigInteger.ZERO)
127
+ }
128
+ type == "address" -> {
129
+ val a = p.contentOrNull?.takeIf { p.isString } ?: throw invalid("address expected")
130
+ word(BigInteger(1, Bytes.fromHex(checkAddress(a))))
131
+ }
132
+ INT_TYPE.matches(type) -> {
133
+ val m = INT_TYPE.find(type)!!
134
+ val signed = m.groupValues[1].isEmpty()
135
+ val bits = m.groupValues[2].ifEmpty { "256" }.toInt()
136
+ if (bits < 8 || bits > 256 || bits % 8 != 0) throw invalid("invalid integer type $type")
137
+ val v = JsValues.toBigIntOrNull(p) ?: throw invalid("integer value expected for $type")
138
+ val (min, max) = if (signed) {
139
+ BigInteger.ONE.shiftLeft(bits - 1).negate() to BigInteger.ONE.shiftLeft(bits - 1).subtract(BigInteger.ONE)
140
+ } else {
141
+ BigInteger.ZERO to BigInteger.ONE.shiftLeft(bits).subtract(BigInteger.ONE)
142
+ }
143
+ if (v < min || v > max) throw invalid("$type value out of range")
144
+ word(if (v.signum() < 0) v.add(TWO_256) else v)
145
+ }
146
+ BYTES_N.matches(type) -> {
147
+ val n = BYTES_N.find(type)!!.groupValues[1].toInt()
148
+ if (n < 1 || n > 32) throw invalid("invalid type $type")
149
+ val bytes = p.contentOrNull?.takeIf { p.isString && it.startsWith("0x") }?.let { Bytes.fromHexOrNull(it) }
150
+ ?: throw invalid("$type must be 0x hex")
151
+ if (bytes.size != n) throw invalid("$type expects $n bytes")
152
+ bytes.copyOf(32)
153
+ }
154
+ else -> throw invalid("unsupported type $type")
155
+ }
156
+ }
157
+
158
+ /** viem isAddress (strict): mixed-case addresses must carry a valid EIP-55 checksum. */
159
+ fun checkAddress(address: String): String {
160
+ if (!ADDRESS.matches(address)) throw invalid("invalid address $address")
161
+ val body = address.substring(2)
162
+ val mixed = body.any { it.isUpperCase() } && body.any { it.isLowerCase() }
163
+ if (mixed && Keys.toChecksumAddress(address) != address) throw invalid("address checksum mismatch $address")
164
+ return address
165
+ }
166
+
167
+ private fun word(v: BigInteger): ByteArray {
168
+ val raw = v.toByteArray()
169
+ val out = ByteArray(32)
170
+ val src = if (raw.size > 32) raw.copyOfRange(raw.size - 32, raw.size) else raw
171
+ src.copyInto(out, 32 - src.size)
172
+ return out
173
+ }
174
+
175
+ private fun invalid(message: String) = walletError(WalletErrorCode.INVALID_REQUEST, message)
176
+ }
@@ -0,0 +1,45 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/EvmAccount.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import kotlinx.serialization.json.JsonObject
5
+ import org.web3j.crypto.ECKeyPair
6
+ import org.web3j.crypto.Hash
7
+ import org.web3j.crypto.Keys
8
+ import org.web3j.crypto.Sign
9
+
10
+ /**
11
+ * secp256k1 account backed by web3j (RFC 6979 deterministic, low-s), producing the same bytes as
12
+ * viem's privateKeyToAccount: signatures are r||s||v with v = 27/28.
13
+ * web3j keeps the key as a BigInteger, which cannot be wiped; the object is short-lived.
14
+ */
15
+ internal class EvmAccount(privateKey: ByteArray) {
16
+ private val keyPair: ECKeyPair = ECKeyPair.create(privateKey)
17
+
18
+ val address: String = Keys.toChecksumAddress(Keys.getAddress(keyPair.publicKey))
19
+
20
+ class Signature(val r: ByteArray, val s: ByteArray, val yParity: Int) {
21
+ fun toHex(): String = "0x" + Bytes.toHex(Bytes.concat(r, s, byteArrayOf((27 + yParity).toByte())))
22
+ }
23
+
24
+ fun signDigest(digest: ByteArray): Signature {
25
+ require(digest.size == 32) { "digest must be 32 bytes" }
26
+ val sig = Sign.signMessage(digest, keyPair, false)
27
+ val v = sig.v[0].toInt() and 0xff
28
+ return Signature(sig.r, sig.s, v - 27)
29
+ }
30
+
31
+ /** personal_sign (EIP-191 version 0x45) over raw bytes. */
32
+ fun signMessage(message: ByteArray): String = signDigest(personalMessageHash(message)).toHex()
33
+
34
+ fun signTypedData(typedData: JsonObject): String = signDigest(Eip712.hashTypedData(typedData)).toHex()
35
+
36
+ fun signTransaction(tx: EvmTransaction): String {
37
+ val sig = signDigest(Hash.sha3(tx.serialize(null)))
38
+ return "0x" + Bytes.toHex(tx.serialize(sig))
39
+ }
40
+
41
+ companion object {
42
+ fun personalMessageHash(message: ByteArray): ByteArray =
43
+ Hash.sha3(Bytes.concat(Bytes.utf8("\u0019Ethereum Signed Message:\n${message.size}"), message))
44
+ }
45
+ }
@@ -0,0 +1,180 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import kotlinx.serialization.json.JsonArray
5
+ import kotlinx.serialization.json.JsonElement
6
+ import kotlinx.serialization.json.JsonNull
7
+ import kotlinx.serialization.json.JsonObject
8
+ import kotlinx.serialization.json.JsonPrimitive
9
+ import kotlinx.serialization.json.contentOrNull
10
+ import org.web3j.rlp.RlpEncoder
11
+ import org.web3j.rlp.RlpList
12
+ import org.web3j.rlp.RlpString
13
+ import org.web3j.rlp.RlpType
14
+ import java.math.BigInteger
15
+
16
+ /**
17
+ * EVM transaction in the frame's input shape (parseTransaction + viem), serialized exactly like
18
+ * viem's serializeTransaction for legacy (EIP-155), EIP-2930 and EIP-1559, including contract
19
+ * creation. RLP comes from web3j.
20
+ */
21
+ internal class EvmTransaction(
22
+ val type: Type,
23
+ val chainId: Long,
24
+ val nonce: Long?,
25
+ val gas: BigInteger?,
26
+ val gasPrice: BigInteger?,
27
+ val maxFeePerGas: BigInteger?,
28
+ val maxPriorityFeePerGas: BigInteger?,
29
+ /** Null for a contract creation. */
30
+ val to: String?,
31
+ val value: BigInteger?,
32
+ val data: ByteArray,
33
+ val accessList: List<AccessListEntry>,
34
+ /** The parsed request as the approval summary sees it. */
35
+ val fields: JsonObject,
36
+ ) {
37
+ enum class Type { LEGACY, EIP2930, EIP1559 }
38
+
39
+ class AccessListEntry(val address: String, val storageKeys: List<ByteArray>)
40
+
41
+ fun serialize(signature: EvmAccount.Signature?): ByteArray {
42
+ val items = ArrayList<RlpType>()
43
+ fun num(v: BigInteger?) = RlpString.create(v ?: BigInteger.ZERO)
44
+ fun numL(v: Long?) = RlpString.create(BigInteger.valueOf(v ?: 0L))
45
+ val toItem = RlpString.create(to?.let { Bytes.fromHex(it) } ?: ByteArray(0))
46
+ val accessItems: RlpType = RlpList(
47
+ accessList.map { e ->
48
+ RlpList(RlpString.create(Bytes.fromHex(e.address)), RlpList(e.storageKeys.map { RlpString.create(it) }))
49
+ },
50
+ )
51
+ when (type) {
52
+ Type.LEGACY -> {
53
+ items += listOf(numL(nonce), num(gasPrice), num(gas), toItem, num(value), RlpString.create(data))
54
+ if (signature != null) {
55
+ val v = if (chainId > 0) {
56
+ BigInteger.valueOf(chainId).multiply(BigInteger.TWO).add(BigInteger.valueOf(35L + signature.yParity))
57
+ } else {
58
+ BigInteger.valueOf(27L + signature.yParity)
59
+ }
60
+ items += listOf(RlpString.create(v), sigInt(signature.r), sigInt(signature.s))
61
+ } else if (chainId > 0) {
62
+ items += listOf(numL(chainId), RlpString.create(ByteArray(0)), RlpString.create(ByteArray(0)))
63
+ }
64
+ return RlpEncoder.encode(RlpList(items))
65
+ }
66
+ Type.EIP2930 -> {
67
+ items += listOf(numL(chainId), numL(nonce), num(gasPrice), num(gas), toItem, num(value), RlpString.create(data), accessItems)
68
+ }
69
+ Type.EIP1559 -> {
70
+ items += listOf(
71
+ numL(chainId), numL(nonce), num(maxPriorityFeePerGas), num(maxFeePerGas), num(gas), toItem, num(value),
72
+ RlpString.create(data), accessItems,
73
+ )
74
+ }
75
+ }
76
+ if (signature != null) {
77
+ items += listOf(RlpString.create(BigInteger.valueOf(signature.yParity.toLong())), sigInt(signature.r), sigInt(signature.s))
78
+ }
79
+ val prefix: Byte = if (type == Type.EIP2930) 0x01 else 0x02
80
+ return byteArrayOf(prefix) + RlpEncoder.encode(RlpList(items))
81
+ }
82
+
83
+ private fun sigInt(bytes: ByteArray) = RlpString.create(BigInteger(1, bytes))
84
+
85
+ companion object {
86
+ private val BIGINT_FIELDS = listOf("value", "gas", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "maxFeePerBlobGas")
87
+ private val NUMBER_FIELDS = listOf("nonce", "chainId")
88
+ private val MAX_UINT256: BigInteger = BigInteger.ONE.shiftLeft(256).subtract(BigInteger.ONE)
89
+
90
+ /** The frame's parseTransaction followed by viem's type inference and assertions. */
91
+ fun parse(input: JsonObject): EvmTransaction {
92
+ val tx = LinkedHashMap<String, JsonElement>(input)
93
+ val big = HashMap<String, BigInteger>()
94
+ for (f in BIGINT_FIELDS + NUMBER_FIELDS) {
95
+ val raw = tx[f] ?: continue
96
+ if (raw is JsonNull) continue
97
+ val v = JsValues.toBigIntOrNull(raw) ?: throw invalid("transaction numeric field invalid")
98
+ big[f] = v
99
+ tx[f] = JsonPrimitive(v)
100
+ }
101
+ val chainIdBig = big["chainId"] ?: throw invalid("chainId required")
102
+ val chainId = chainIdBig.toLong()
103
+ val nonce = big["nonce"]?.toLong()
104
+
105
+ val explicit = (tx["type"] as? JsonPrimitive)?.contentOrNull
106
+ val type = when (explicit) {
107
+ null -> when {
108
+ big["maxFeePerBlobGas"] != null || tx["blobs"].present() || tx["blobVersionedHashes"].present() ->
109
+ throw walletError(WalletErrorCode.UNSUPPORTED_TYPE, "blob transactions are not supported")
110
+ tx["authorizationList"].present() ->
111
+ throw walletError(WalletErrorCode.UNSUPPORTED_TYPE, "EIP-7702 transactions are not supported")
112
+ big["maxFeePerGas"] != null || big["maxPriorityFeePerGas"] != null -> Type.EIP1559
113
+ big["gasPrice"] != null -> if (tx["accessList"].present()) Type.EIP2930 else Type.LEGACY
114
+ else -> throw invalid("cannot infer the transaction type")
115
+ }
116
+ "legacy", "0x0" -> Type.LEGACY
117
+ "eip2930", "0x1" -> Type.EIP2930
118
+ "eip1559", "0x2" -> Type.EIP1559
119
+ else -> throw walletError(WalletErrorCode.UNSUPPORTED_TYPE, "transaction type $explicit is not supported")
120
+ }
121
+
122
+ val toRaw = tx["to"]
123
+ val to = when {
124
+ toRaw == null || toRaw is JsonNull -> null
125
+ toRaw is JsonPrimitive && toRaw.isString && toRaw.content.isEmpty() -> null
126
+ toRaw is JsonPrimitive && toRaw.isString -> Eip712.checkAddress(toRaw.content)
127
+ else -> throw invalid("to must be an address")
128
+ }
129
+ val dataRaw = tx["data"]
130
+ val data = when {
131
+ dataRaw == null || dataRaw is JsonNull -> ByteArray(0)
132
+ dataRaw is JsonPrimitive && dataRaw.isString && dataRaw.content.startsWith("0x") ->
133
+ Bytes.fromHexOrNull(dataRaw.content) ?: throw invalid("data must be hex")
134
+ else -> throw invalid("data must be 0x hex")
135
+ }
136
+ val accessList = parseAccessList(tx["accessList"])
137
+
138
+ val gasPrice = big["gasPrice"]
139
+ val maxFee = big["maxFeePerGas"]
140
+ val maxTip = big["maxPriorityFeePerGas"]
141
+ if (chainId <= 0 && type != Type.LEGACY) throw invalid("invalid chainId")
142
+ if (type == Type.LEGACY && tx["chainId"] != null && chainId <= 0) throw invalid("invalid chainId")
143
+ when (type) {
144
+ Type.LEGACY, Type.EIP2930 -> {
145
+ if (maxFee != null || maxTip != null) throw invalid("maxFeePerGas/maxPriorityFeePerGas are not valid for this type")
146
+ if (gasPrice != null && gasPrice > MAX_UINT256) throw invalid("gasPrice too high")
147
+ }
148
+ Type.EIP1559 -> {
149
+ if (maxFee != null && maxFee > MAX_UINT256) throw invalid("maxFeePerGas too high")
150
+ if (maxTip != null && maxFee != null && maxTip > maxFee) throw invalid("maxPriorityFeePerGas above maxFeePerGas")
151
+ }
152
+ }
153
+ for ((k, v) in big) if (v.signum() < 0) throw invalid("$k must not be negative")
154
+ if (type == Type.LEGACY && accessList.isNotEmpty()) throw invalid("accessList is not valid for legacy transactions")
155
+
156
+ return EvmTransaction(
157
+ type, chainId, nonce, big["gas"], gasPrice, maxFee, maxTip, to, big["value"], data, accessList, JsonObject(tx),
158
+ )
159
+ }
160
+
161
+ private fun parseAccessList(raw: JsonElement?): List<AccessListEntry> {
162
+ if (raw == null || raw is JsonNull) return emptyList()
163
+ val arr = raw as? JsonArray ?: throw invalid("accessList must be an array")
164
+ return arr.map { item ->
165
+ val o = item as? JsonObject ?: throw invalid("accessList entry must be an object")
166
+ val address = (o["address"] as? JsonPrimitive)?.contentOrNull ?: throw invalid("accessList address missing")
167
+ val keys = (o["storageKeys"] as? JsonArray ?: JsonArray(emptyList())).map { k ->
168
+ val hex = (k as? JsonPrimitive)?.contentOrNull ?: throw invalid("storage key must be hex")
169
+ if (hex.length != 66) throw invalid("storage key must be 32 bytes")
170
+ Bytes.fromHexOrNull(hex) ?: throw invalid("storage key must be hex")
171
+ }
172
+ AccessListEntry(Eip712.checkAddress(address), keys)
173
+ }
174
+ }
175
+
176
+ private fun JsonElement?.present(): Boolean = this != null && this !is JsonNull
177
+
178
+ private fun invalid(message: String) = walletError(WalletErrorCode.INVALID_REQUEST, message)
179
+ }
180
+ }
@@ -0,0 +1,44 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/GfTables.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ // GF(2^8) tables of the shamir-secret-sharing JS library (polynomial x^8+x^4+x^3+x+1, generator 0xe5),
5
+ // copied verbatim so combine/split/deriveShare produce the same bytes as the web frame.
6
+ internal object GfTables {
7
+ val LOG: IntArray = intArrayOf(
8
+ 0x00, 0xff, 0xc8, 0x08, 0x91, 0x10, 0xd0, 0x36, 0x5a, 0x3e, 0xd8, 0x43, 0x99, 0x77, 0xfe, 0x18,
9
+ 0x23, 0x20, 0x07, 0x70, 0xa1, 0x6c, 0x0c, 0x7f, 0x62, 0x8b, 0x40, 0x46, 0xc7, 0x4b, 0xe0, 0x0e,
10
+ 0xeb, 0x16, 0xe8, 0xad, 0xcf, 0xcd, 0x39, 0x53, 0x6a, 0x27, 0x35, 0x93, 0xd4, 0x4e, 0x48, 0xc3,
11
+ 0x2b, 0x79, 0x54, 0x28, 0x09, 0x78, 0x0f, 0x21, 0x90, 0x87, 0x14, 0x2a, 0xa9, 0x9c, 0xd6, 0x74,
12
+ 0xb4, 0x7c, 0xde, 0xed, 0xb1, 0x86, 0x76, 0xa4, 0x98, 0xe2, 0x96, 0x8f, 0x02, 0x32, 0x1c, 0xc1,
13
+ 0x33, 0xee, 0xef, 0x81, 0xfd, 0x30, 0x5c, 0x13, 0x9d, 0x29, 0x17, 0xc4, 0x11, 0x44, 0x8c, 0x80,
14
+ 0xf3, 0x73, 0x42, 0x1e, 0x1d, 0xb5, 0xf0, 0x12, 0xd1, 0x5b, 0x41, 0xa2, 0xd7, 0x2c, 0xe9, 0xd5,
15
+ 0x59, 0xcb, 0x50, 0xa8, 0xdc, 0xfc, 0xf2, 0x56, 0x72, 0xa6, 0x65, 0x2f, 0x9f, 0x9b, 0x3d, 0xba,
16
+ 0x7d, 0xc2, 0x45, 0x82, 0xa7, 0x57, 0xb6, 0xa3, 0x7a, 0x75, 0x4f, 0xae, 0x3f, 0x37, 0x6d, 0x47,
17
+ 0x61, 0xbe, 0xab, 0xd3, 0x5f, 0xb0, 0x58, 0xaf, 0xca, 0x5e, 0xfa, 0x85, 0xe4, 0x4d, 0x8a, 0x05,
18
+ 0xfb, 0x60, 0xb7, 0x7b, 0xb8, 0x26, 0x4a, 0x67, 0xc6, 0x1a, 0xf8, 0x69, 0x25, 0xb3, 0xdb, 0xbd,
19
+ 0x66, 0xdd, 0xf1, 0xd2, 0xdf, 0x03, 0x8d, 0x34, 0xd9, 0x92, 0x0d, 0x63, 0x55, 0xaa, 0x49, 0xec,
20
+ 0xbc, 0x95, 0x3c, 0x84, 0x0b, 0xf5, 0xe6, 0xe7, 0xe5, 0xac, 0x7e, 0x6e, 0xb9, 0xf9, 0xda, 0x8e,
21
+ 0x9a, 0xc9, 0x24, 0xe1, 0x0a, 0x15, 0x6b, 0x3a, 0xa0, 0x51, 0xf4, 0xea, 0xb2, 0x97, 0x9e, 0x5d,
22
+ 0x22, 0x88, 0x94, 0xce, 0x19, 0x01, 0x71, 0x4c, 0xa5, 0xe3, 0xc5, 0x31, 0xbb, 0xcc, 0x1f, 0x2d,
23
+ 0x3b, 0x52, 0x6f, 0xf6, 0x2e, 0x89, 0xf7, 0xc0, 0x68, 0x1b, 0x64, 0x04, 0x06, 0xbf, 0x83, 0x38,
24
+ )
25
+
26
+ val EXP: IntArray = intArrayOf(
27
+ 0x01, 0xe5, 0x4c, 0xb5, 0xfb, 0x9f, 0xfc, 0x12, 0x03, 0x34, 0xd4, 0xc4, 0x16, 0xba, 0x1f, 0x36,
28
+ 0x05, 0x5c, 0x67, 0x57, 0x3a, 0xd5, 0x21, 0x5a, 0x0f, 0xe4, 0xa9, 0xf9, 0x4e, 0x64, 0x63, 0xee,
29
+ 0x11, 0x37, 0xe0, 0x10, 0xd2, 0xac, 0xa5, 0x29, 0x33, 0x59, 0x3b, 0x30, 0x6d, 0xef, 0xf4, 0x7b,
30
+ 0x55, 0xeb, 0x4d, 0x50, 0xb7, 0x2a, 0x07, 0x8d, 0xff, 0x26, 0xd7, 0xf0, 0xc2, 0x7e, 0x09, 0x8c,
31
+ 0x1a, 0x6a, 0x62, 0x0b, 0x5d, 0x82, 0x1b, 0x8f, 0x2e, 0xbe, 0xa6, 0x1d, 0xe7, 0x9d, 0x2d, 0x8a,
32
+ 0x72, 0xd9, 0xf1, 0x27, 0x32, 0xbc, 0x77, 0x85, 0x96, 0x70, 0x08, 0x69, 0x56, 0xdf, 0x99, 0x94,
33
+ 0xa1, 0x90, 0x18, 0xbb, 0xfa, 0x7a, 0xb0, 0xa7, 0xf8, 0xab, 0x28, 0xd6, 0x15, 0x8e, 0xcb, 0xf2,
34
+ 0x13, 0xe6, 0x78, 0x61, 0x3f, 0x89, 0x46, 0x0d, 0x35, 0x31, 0x88, 0xa3, 0x41, 0x80, 0xca, 0x17,
35
+ 0x5f, 0x53, 0x83, 0xfe, 0xc3, 0x9b, 0x45, 0x39, 0xe1, 0xf5, 0x9e, 0x19, 0x5e, 0xb6, 0xcf, 0x4b,
36
+ 0x38, 0x04, 0xb9, 0x2b, 0xe2, 0xc1, 0x4a, 0xdd, 0x48, 0x0c, 0xd0, 0x7d, 0x3d, 0x58, 0xde, 0x7c,
37
+ 0xd8, 0x14, 0x6b, 0x87, 0x47, 0xe8, 0x79, 0x84, 0x73, 0x3c, 0xbd, 0x92, 0xc9, 0x23, 0x8b, 0x97,
38
+ 0x95, 0x44, 0xdc, 0xad, 0x40, 0x65, 0x86, 0xa2, 0xa4, 0xcc, 0x7f, 0xec, 0xc0, 0xaf, 0x91, 0xfd,
39
+ 0xf7, 0x4f, 0x81, 0x2f, 0x5b, 0xea, 0xa8, 0x1c, 0x02, 0xd1, 0x98, 0x71, 0xed, 0x25, 0xe3, 0x24,
40
+ 0x06, 0x68, 0xb3, 0x93, 0x2c, 0x6f, 0x3e, 0x6c, 0x0a, 0xb8, 0xce, 0xae, 0x74, 0xb1, 0x42, 0xb4,
41
+ 0x1e, 0xd3, 0x49, 0xe9, 0x9c, 0xc8, 0xc6, 0xc7, 0x22, 0x6e, 0xdb, 0x20, 0xbf, 0x43, 0x51, 0x52,
42
+ 0x66, 0xb2, 0x76, 0x60, 0xda, 0xc5, 0xf3, 0xf6, 0xaa, 0xcd, 0x9a, 0xa0, 0x75, 0x54, 0x0e, 0x01,
43
+ )
44
+ }
@@ -0,0 +1,130 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/HdWallet.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import org.web3j.crypto.Bip32ECKeyPair
5
+ import org.web3j.crypto.MnemonicUtils
6
+ import org.web3j.utils.Numeric
7
+
8
+ /**
9
+ * The shared secret is BIP39 entropy (16 bytes = 12 words); keys are m/44'/60'/0'/0/{index}.
10
+ * A 32-byte secret is a raw secp256k1 key from before HD wallets and has only account 0.
11
+ * Same rules as `apps/wallet/src/shares.ts`.
12
+ */
13
+ internal object HdWallet {
14
+ const val HD_ENTROPY_BYTES = 16
15
+ const val MAX_ACCOUNT_INDEX: Long = 0x7fffffff
16
+
17
+ private const val H = Bip32ECKeyPair.HARDENED_BIT
18
+ private val ACCOUNT_PARENT = intArrayOf(44 or H, 60 or H, 0 or H, 0)
19
+
20
+ fun evmPath(index: Long): String = "m/44'/60'/0'/0/$index"
21
+
22
+ fun generateSecret(): ByteArray = Bytes.randomBytes(HD_ENTROPY_BYTES)
23
+
24
+ fun isHdSecret(secret: ByteArray): Boolean = secret.size == HD_ENTROPY_BYTES
25
+
26
+ fun isAccountIndex(index: Long): Boolean = index in 0..MAX_ACCOUNT_INDEX
27
+
28
+ fun mnemonicOf(entropy: ByteArray): String = MnemonicUtils.generateMnemonic(entropy)
29
+
30
+ /** BIP39 seed (PBKDF2-HMAC-SHA512, 2048 rounds, salt "mnemonic"). Callers wipe it. */
31
+ fun seedOf(entropy: ByteArray): ByteArray = MnemonicUtils.generateSeed(mnemonicOf(entropy), "")
32
+
33
+ private fun accountParent(secret: ByteArray): Bip32ECKeyPair {
34
+ val seed = seedOf(secret)
35
+ try {
36
+ return Bip32ECKeyPair.deriveKeyPair(Bip32ECKeyPair.generateKeyPair(seed), ACCOUNT_PARENT)
37
+ } finally {
38
+ seed.fill(0)
39
+ }
40
+ }
41
+
42
+ /** The EVM private key at account [index]. Callers wipe the result. */
43
+ fun privateKey(secret: ByteArray, index: Long = 0): ByteArray {
44
+ if (!isAccountIndex(index)) throw IllegalArgumentException("invalid account index")
45
+ if (secret.size == 32) {
46
+ if (index != 0L) throw walletError(WalletErrorCode.ACCOUNT_UNAVAILABLE, "this wallet has only account 0")
47
+ return secret.copyOf()
48
+ }
49
+ if (!isHdSecret(secret)) throw walletError(WalletErrorCode.INTERNAL, "unsupported wallet secret")
50
+ val node = Bip32ECKeyPair.deriveKeyPair(accountParent(secret), intArrayOf(index.toInt()))
51
+ return Numeric.toBytesPadded(node.privateKey, 32)
52
+ }
53
+
54
+ fun account(secret: ByteArray, index: Long = 0): EvmAccount {
55
+ val key = privateKey(secret, index)
56
+ try {
57
+ return EvmAccount(key)
58
+ } finally {
59
+ key.fill(0)
60
+ }
61
+ }
62
+
63
+ fun address(secret: ByteArray, index: Long = 0): String = account(secret, index).address
64
+
65
+ /** Addresses of accounts start..start+count-1; the seed is derived once for the range. */
66
+ fun addresses(secret: ByteArray, start: Long, count: Int): List<String> {
67
+ if (!isAccountIndex(start) || count < 1 || !isAccountIndex(start + count - 1)) {
68
+ throw IllegalArgumentException("invalid account range")
69
+ }
70
+ if (secret.size == 32) {
71
+ if (start != 0L || count != 1) throw walletError(WalletErrorCode.ACCOUNT_UNAVAILABLE, "this wallet has only account 0")
72
+ return listOf(address(secret, 0))
73
+ }
74
+ if (!isHdSecret(secret)) throw walletError(WalletErrorCode.INTERNAL, "unsupported wallet secret")
75
+ val parent = accountParent(secret)
76
+ return (0 until count).map { i ->
77
+ val node = Bip32ECKeyPair.deriveKeyPair(parent, intArrayOf((start + i).toInt()))
78
+ val key = Numeric.toBytesPadded(node.privateKey, 32)
79
+ try {
80
+ EvmAccount(key).address
81
+ } finally {
82
+ key.fill(0)
83
+ }
84
+ }
85
+ }
86
+
87
+ class SplitShares(val device: ByteArray, val company: ByteArray, val backup: ByteArray?) {
88
+ fun wipe() = Bytes.wipe(device, company, backup)
89
+ }
90
+
91
+ fun splitUnprotected(secret: ByteArray): SplitShares {
92
+ val s = Shamir.split(secret, 2, 2)
93
+ return SplitShares(s[0], s[1], null)
94
+ }
95
+
96
+ fun splitProtected(secret: ByteArray): SplitShares {
97
+ val s = Shamir.split(secret, 3, 2)
98
+ return SplitShares(s[0], s[1], s[2])
99
+ }
100
+
101
+ fun assemble(a: ByteArray, b: ByteArray): ByteArray = Shamir.combine(listOf(a, b))
102
+
103
+ /**
104
+ * One more share on the same 2-of-n polynomial as [a] and [b] (see shares.ts deriveShare):
105
+ * shifting both x coordinates by x3 and interpolating at zero yields f(x3).
106
+ */
107
+ fun deriveShare(a: ByteArray, b: ByteArray, pickX: () -> Int = ::randomX): ByteArray {
108
+ if (a.size != b.size || a.size < 2) throw IllegalArgumentException("share length mismatch")
109
+ val xa = a[a.size - 1].toInt() and 0xff
110
+ val xb = b[b.size - 1].toInt() and 0xff
111
+ var x3 = 0
112
+ while (x3 == 0 || x3 == xa || x3 == xb) x3 = pickX() and 0xff
113
+ val shiftedA = a.copyOf()
114
+ val shiftedB = b.copyOf()
115
+ shiftedA[a.size - 1] = (xa xor x3).toByte()
116
+ shiftedB[b.size - 1] = (xb xor x3).toByte()
117
+ try {
118
+ val y = Shamir.combine(listOf(shiftedA, shiftedB))
119
+ val share = ByteArray(a.size)
120
+ y.copyInto(share)
121
+ share[a.size - 1] = x3.toByte()
122
+ y.fill(0)
123
+ return share
124
+ } finally {
125
+ Bytes.wipe(shiftedA, shiftedB)
126
+ }
127
+ }
128
+
129
+ private fun randomX(): Int = Bytes.randomBytes(1)[0].toInt() and 0xff
130
+ }
@@ -0,0 +1,82 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/Http.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import kotlinx.coroutines.suspendCancellableCoroutine
5
+ import kotlinx.serialization.json.Json
6
+ import kotlinx.serialization.json.JsonElement
7
+ import kotlinx.serialization.json.JsonObject
8
+ import okhttp3.Call
9
+ import okhttp3.Callback
10
+ import okhttp3.MediaType.Companion.toMediaType
11
+ import okhttp3.OkHttpClient
12
+ import okhttp3.Request
13
+ import okhttp3.RequestBody.Companion.toRequestBody
14
+ import okhttp3.Response
15
+ import java.io.IOException
16
+ import java.util.concurrent.TimeUnit
17
+ import kotlin.coroutines.resume
18
+ import kotlin.coroutines.resumeWithException
19
+
20
+ internal class HttpResult(val status: Int, val text: String) {
21
+ val ok: Boolean get() = status in 200..299
22
+
23
+ /** The body as a JSON object; `{}` when it is empty or not an object (like the frame's `asRecord`). */
24
+ val json: JsonObject by lazy {
25
+ if (text.isBlank()) {
26
+ JsonObject(emptyMap())
27
+ } else {
28
+ runCatching { Json.parseToJsonElement(text) as? JsonObject }.getOrNull() ?: JsonObject(emptyMap())
29
+ }
30
+ }
31
+ }
32
+
33
+ /** Keep in sync with `version` in authon/build.gradle.kts. */
34
+ internal const val SDK_VERSION = "0.1.0"
35
+
36
+ /** HTTP helper shared by the wallet and the auth core; lives here so the wallet package stands alone. */
37
+ internal object Http {
38
+ private val JSON_MEDIA = "application/json".toMediaType()
39
+
40
+ val defaultClient: OkHttpClient by lazy {
41
+ OkHttpClient.Builder()
42
+ .connectTimeout(15, TimeUnit.SECONDS)
43
+ .readTimeout(30, TimeUnit.SECONDS)
44
+ .writeTimeout(30, TimeUnit.SECONDS)
45
+ .followRedirects(false)
46
+ .build()
47
+ }
48
+
49
+ fun userAgent(): String = "authon-android/$SDK_VERSION"
50
+
51
+ fun request(
52
+ url: String,
53
+ method: String,
54
+ headers: Map<String, String>,
55
+ body: JsonElement?,
56
+ ): Request {
57
+ val builder = Request.Builder().url(url)
58
+ for ((k, v) in headers) builder.header(k, v)
59
+ val payload = body?.toString()?.toRequestBody(JSON_MEDIA)
60
+ builder.method(method, payload ?: if (method == "POST" || method == "PUT" || method == "PATCH") ByteArray(0).toRequestBody(null) else null)
61
+ return builder.build()
62
+ }
63
+
64
+ /** Executes the call; throws IOException on network failure. Cancelling the coroutine cancels the call. */
65
+ suspend fun execute(client: OkHttpClient, request: Request): HttpResult =
66
+ suspendCancellableCoroutine { cont ->
67
+ val call = client.newCall(request)
68
+ cont.invokeOnCancellation { call.cancel() }
69
+ call.enqueue(
70
+ object : Callback {
71
+ override fun onFailure(call: Call, e: IOException) {
72
+ if (cont.isActive) cont.resumeWithException(e)
73
+ }
74
+
75
+ override fun onResponse(call: Call, response: Response) {
76
+ val result = response.use { HttpResult(it.code, it.body?.string() ?: "") }
77
+ if (cont.isActive) cont.resume(result)
78
+ }
79
+ },
80
+ )
81
+ }
82
+ }