@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.
- package/README.md +221 -2
- package/android/build.gradle +40 -0
- package/android/consumer-rules.pro +10 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +181 -0
- package/android/src/vendor/GENERATED.md +4 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +191 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DeviceStore.kt +170 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt +141 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Eip712.kt +176 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmAccount.kt +45 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt +180 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/GfTables.kt +44 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/HdWallet.kt +130 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +82 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/JsValues.kt +83 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +232 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Prf.kt +111 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Proof.kt +134 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Shamir.kt +106 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/SignSummary.kt +335 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +353 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +58 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +449 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +71 -0
- package/build/authonContext.d.ts +19 -0
- package/build/authonContext.d.ts.map +1 -0
- package/build/authonContext.js +20 -0
- package/build/authonContext.js.map +1 -0
- package/build/client.d.ts +76 -0
- package/build/client.d.ts.map +1 -0
- package/build/client.js +349 -0
- package/build/client.js.map +1 -0
- package/build/errors.d.ts +18 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +9 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +7 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/native.d.ts +39 -0
- package/build/native.d.ts.map +1 -0
- package/build/native.js +12 -0
- package/build/native.js.map +1 -0
- package/build/types.d.ts +121 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +3 -0
- package/build/types.js.map +1 -0
- package/build/useAuthonWallet.d.ts +25 -0
- package/build/useAuthonWallet.d.ts.map +1 -0
- package/build/useAuthonWallet.js +77 -0
- package/build/useAuthonWallet.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/AuthonReactNativeWallet.podspec +37 -0
- package/ios/AuthonWalletModule.swift +190 -0
- package/ios/vendor/AuthonWallet/AuthonWallet.swift +212 -0
- package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
- package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
- package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +104 -0
- package/ios/vendor/AuthonWallet/Crypto/Shamir.swift +123 -0
- package/ios/vendor/AuthonWallet/EVM/Address.swift +57 -0
- package/ios/vendor/AuthonWallet/EVM/EvmAccount.swift +48 -0
- package/ios/vendor/AuthonWallet/EVM/Transaction.swift +165 -0
- package/ios/vendor/AuthonWallet/EVM/TypedData.swift +193 -0
- package/ios/vendor/AuthonWallet/Support/BigInteger.swift +220 -0
- package/ios/vendor/AuthonWallet/Support/Bytes.swift +89 -0
- package/ios/vendor/AuthonWallet/Support/JSONValue.swift +341 -0
- package/ios/vendor/AuthonWallet/UI/WalletApprover.swift +250 -0
- package/ios/vendor/AuthonWallet/Wallet/Backup.swift +69 -0
- package/ios/vendor/AuthonWallet/Wallet/DeviceStore.swift +351 -0
- package/ios/vendor/AuthonWallet/Wallet/Passkeys.swift +190 -0
- package/ios/vendor/AuthonWallet/Wallet/Proof.swift +143 -0
- package/ios/vendor/AuthonWallet/Wallet/Shares.swift +116 -0
- package/ios/vendor/AuthonWallet/Wallet/Summary.swift +342 -0
- package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +350 -0
- package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +555 -0
- package/ios/vendor/AuthonWallet/WalletError.swift +54 -0
- package/ios/vendor/CKeccak/LICENSE +22 -0
- package/ios/vendor/CKeccak/README.md +27 -0
- package/ios/vendor/CKeccak/byte_order.h +59 -0
- package/ios/vendor/CKeccak/include/ckeccak.h +9 -0
- package/ios/vendor/CKeccak/memzero.c +75 -0
- package/ios/vendor/CKeccak/memzero.h +8 -0
- package/ios/vendor/CKeccak/options.h +99 -0
- package/ios/vendor/CKeccak/sha3.c +413 -0
- package/ios/vendor/CKeccak/sha3.h +89 -0
- package/ios/vendor/CSecp256k1/COPYING +19 -0
- package/ios/vendor/CSecp256k1/README.md +35 -0
- package/ios/vendor/CSecp256k1/build/precomputed_ecmult.c +3 -0
- package/ios/vendor/CSecp256k1/build/precomputed_ecmult_gen.c +3 -0
- package/ios/vendor/CSecp256k1/build/secp256k1.c +3 -0
- package/ios/vendor/CSecp256k1/include/secp256k1.h +899 -0
- package/ios/vendor/CSecp256k1/include/secp256k1_preallocated.h +134 -0
- package/ios/vendor/CSecp256k1/include/secp256k1_recovery.h +113 -0
- package/ios/vendor/CSecp256k1/src/assumptions.h +87 -0
- package/ios/vendor/CSecp256k1/src/checkmem.h +102 -0
- package/ios/vendor/CSecp256k1/src/ecdsa.h +21 -0
- package/ios/vendor/CSecp256k1/src/ecdsa_impl.h +304 -0
- package/ios/vendor/CSecp256k1/src/eckey.h +25 -0
- package/ios/vendor/CSecp256k1/src/eckey_impl.h +92 -0
- package/ios/vendor/CSecp256k1/src/ecmult.h +61 -0
- package/ios/vendor/CSecp256k1/src/ecmult_const.h +38 -0
- package/ios/vendor/CSecp256k1/src/ecmult_const_impl.h +399 -0
- package/ios/vendor/CSecp256k1/src/ecmult_gen.h +143 -0
- package/ios/vendor/CSecp256k1/src/ecmult_gen_impl.h +341 -0
- package/ios/vendor/CSecp256k1/src/ecmult_impl.h +853 -0
- package/ios/vendor/CSecp256k1/src/field.h +350 -0
- package/ios/vendor/CSecp256k1/src/field_10x26.h +57 -0
- package/ios/vendor/CSecp256k1/src/field_10x26_impl.h +1232 -0
- package/ios/vendor/CSecp256k1/src/field_5x52.h +62 -0
- package/ios/vendor/CSecp256k1/src/field_5x52_impl.h +522 -0
- package/ios/vendor/CSecp256k1/src/field_5x52_int128_impl.h +274 -0
- package/ios/vendor/CSecp256k1/src/field_impl.h +457 -0
- package/ios/vendor/CSecp256k1/src/group.h +212 -0
- package/ios/vendor/CSecp256k1/src/group_impl.h +974 -0
- package/ios/vendor/CSecp256k1/src/hash.h +44 -0
- package/ios/vendor/CSecp256k1/src/hash_impl.h +299 -0
- package/ios/vendor/CSecp256k1/src/hsort.h +33 -0
- package/ios/vendor/CSecp256k1/src/hsort_impl.h +125 -0
- package/ios/vendor/CSecp256k1/src/int128.h +90 -0
- package/ios/vendor/CSecp256k1/src/int128_impl.h +18 -0
- package/ios/vendor/CSecp256k1/src/int128_native.h +19 -0
- package/ios/vendor/CSecp256k1/src/int128_native_impl.h +94 -0
- package/ios/vendor/CSecp256k1/src/int128_struct.h +14 -0
- package/ios/vendor/CSecp256k1/src/int128_struct_impl.h +205 -0
- package/ios/vendor/CSecp256k1/src/modinv32.h +43 -0
- package/ios/vendor/CSecp256k1/src/modinv32_impl.h +725 -0
- package/ios/vendor/CSecp256k1/src/modinv64.h +47 -0
- package/ios/vendor/CSecp256k1/src/modinv64_impl.h +780 -0
- package/ios/vendor/CSecp256k1/src/modules/recovery/main_impl.h +159 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult.c +16456 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult.h +38 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.c +1779 -0
- package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.h +26 -0
- package/ios/vendor/CSecp256k1/src/scalar.h +105 -0
- package/ios/vendor/CSecp256k1/src/scalar_4x64.h +19 -0
- package/ios/vendor/CSecp256k1/src/scalar_4x64_impl.h +1000 -0
- package/ios/vendor/CSecp256k1/src/scalar_8x32.h +19 -0
- package/ios/vendor/CSecp256k1/src/scalar_8x32_impl.h +816 -0
- package/ios/vendor/CSecp256k1/src/scalar_impl.h +321 -0
- package/ios/vendor/CSecp256k1/src/scalar_low.h +24 -0
- package/ios/vendor/CSecp256k1/src/scalar_low_impl.h +206 -0
- package/ios/vendor/CSecp256k1/src/scratch.h +44 -0
- package/ios/vendor/CSecp256k1/src/scratch_impl.h +99 -0
- package/ios/vendor/CSecp256k1/src/secp256k1.c +831 -0
- package/ios/vendor/CSecp256k1/src/selftest.h +32 -0
- package/ios/vendor/CSecp256k1/src/util.h +451 -0
- package/ios/vendor/GENERATED.md +4 -0
- package/package.json +65 -3
- package/src/authonContext.ts +30 -0
- package/src/client.ts +375 -0
- package/src/errors.ts +52 -0
- package/src/index.ts +21 -0
- package/src/native.ts +50 -0
- package/src/types.ts +120 -0
- 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
|
+
}
|