@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,108 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Crypto/HDKey.swift. Do not edit: change the source and run the script.
2
+ import CommonCrypto
3
+ import CryptoKit
4
+ import Foundation
5
+
6
+ /// BIP39 (English) for 128..256-bit entropy, as @scure/bip39.
7
+ enum BIP39 {
8
+ static func mnemonic(entropy: [UInt8]) throws -> String {
9
+ guard entropy.count >= 16, entropy.count <= 32, entropy.count % 4 == 0 else {
10
+ throw WalletError(.internal, "invalid entropy length")
11
+ }
12
+ let checksumBits = entropy.count / 4
13
+ let hash = Bytes.sha256(entropy)
14
+ var bits = [Bool]()
15
+ bits.reserveCapacity(entropy.count * 8 + checksumBits)
16
+ for b in entropy { for i in (0..<8).reversed() { bits.append((b >> UInt8(i)) & 1 == 1) } }
17
+ for i in 0..<checksumBits { bits.append((hash[i / 8] >> UInt8(7 - i % 8)) & 1 == 1) }
18
+ var words = [String]()
19
+ for w in 0..<(bits.count / 11) {
20
+ var index = 0
21
+ for j in 0..<11 { index = index << 1 | (bits[w * 11 + j] ? 1 : 0) }
22
+ words.append(bip39EnglishWordlist[index])
23
+ }
24
+ return words.joined(separator: " ")
25
+ }
26
+
27
+ /// PBKDF2-HMAC-SHA512, 2048 rounds, salt "mnemonic" (empty passphrase).
28
+ static func seed(mnemonic: String) throws -> [UInt8] {
29
+ var password = Array(mnemonic.decomposedStringWithCompatibilityMapping.utf8)
30
+ defer { password.wipe() }
31
+ let salt = Array("mnemonic".utf8)
32
+ var out = [UInt8](repeating: 0, count: 64)
33
+ let status = password.withUnsafeBufferPointer { pw in
34
+ CCKeyDerivationPBKDF(
35
+ CCPBKDFAlgorithm(kCCPBKDF2),
36
+ UnsafeRawPointer(pw.baseAddress!).assumingMemoryBound(to: CChar.self), pw.count,
37
+ salt, salt.count,
38
+ CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512), 2048,
39
+ &out, out.count
40
+ )
41
+ }
42
+ guard status == kCCSuccess else { throw WalletError(.internal, "seed derivation failed") }
43
+ return out
44
+ }
45
+ }
46
+
47
+ /// BIP32 private derivation over secp256k1 (scalar addition and public keys in libsecp256k1).
48
+ struct HDNode {
49
+ private(set) var key: [UInt8]
50
+ private(set) var chainCode: [UInt8]
51
+
52
+ static let hardened: UInt32 = 0x8000_0000
53
+
54
+ init(seed: [UInt8]) throws {
55
+ var i = HDNode.hmac(key: Array("Bitcoin seed".utf8), data: seed)
56
+ defer { i.wipe() }
57
+ let k = Array(i[0..<32])
58
+ guard Secp256k1.isValidPrivateKey(k) else { throw WalletError(.internal, "invalid master key") }
59
+ key = k
60
+ chainCode = Array(i[32..<64])
61
+ }
62
+
63
+ private init(key: [UInt8], chainCode: [UInt8]) {
64
+ self.key = key
65
+ self.chainCode = chainCode
66
+ }
67
+
68
+ func child(_ index: UInt32) throws -> HDNode {
69
+ var data: [UInt8]
70
+ if index >= HDNode.hardened {
71
+ data = [0] + key
72
+ } else {
73
+ data = try Secp256k1.publicKey(of: key, compressed: true)
74
+ }
75
+ data += [UInt8(index >> 24), UInt8(truncatingIfNeeded: index >> 16), UInt8(truncatingIfNeeded: index >> 8), UInt8(truncatingIfNeeded: index)]
76
+ var i = HDNode.hmac(key: chainCode, data: data)
77
+ defer {
78
+ i.wipe()
79
+ data.wipe()
80
+ }
81
+ let childKey = try Secp256k1.addTweak(key, Array(i[0..<32]))
82
+ return HDNode(key: childKey, chainCode: Array(i[32..<64]))
83
+ }
84
+
85
+ /// "m/44'/60'/0'/0/5"
86
+ func derive(_ path: String) throws -> HDNode {
87
+ let parts = path.split(separator: "/")
88
+ guard parts.first == "m" else { throw WalletError(.internal, "invalid path") }
89
+ var node = self
90
+ for p in parts.dropFirst() {
91
+ let hard = p.hasSuffix("'")
92
+ guard let n = UInt32(hard ? p.dropLast() : p), n < HDNode.hardened else { throw WalletError(.internal, "invalid path") }
93
+ let next = try node.child(hard ? n + HDNode.hardened : n)
94
+ node.wipe()
95
+ node = next
96
+ }
97
+ return node
98
+ }
99
+
100
+ mutating func wipe() {
101
+ key.wipe()
102
+ chainCode.wipe()
103
+ }
104
+
105
+ private static func hmac(key: [UInt8], data: [UInt8]) -> [UInt8] {
106
+ Array(HMAC<SHA512>.authenticationCode(for: data, using: SymmetricKey(data: key)))
107
+ }
108
+ }
@@ -0,0 +1,128 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Crypto/Secp256k1.swift. Do not edit: change the source and run the script.
2
+ #if canImport(CKeccak)
3
+ import CKeccak
4
+ #endif
5
+ #if canImport(CSecp256k1)
6
+ import CSecp256k1
7
+ #endif
8
+ import Foundation
9
+
10
+ enum Keccak {
11
+ static func hash256(_ data: [UInt8]) -> [UInt8] {
12
+ var out = [UInt8](repeating: 0, count: 32)
13
+ data.withUnsafeBufferPointer { input in
14
+ out.withUnsafeMutableBufferPointer { keccak_256(input.baseAddress, input.count, $0.baseAddress) }
15
+ }
16
+ return out
17
+ }
18
+ }
19
+
20
+ /// libsecp256k1 (bitcoin-core) wrapper. All curve arithmetic happens in the vendored C library.
21
+ enum Secp256k1 {
22
+ struct RecoverableSignature {
23
+ var r: [UInt8]
24
+ var s: [UInt8]
25
+ /// 0 or 1.
26
+ var recoveryId: Int
27
+ }
28
+
29
+ private static let context: OpaquePointer = {
30
+ let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_NONE))!
31
+ // Side-channel blinding of the signing context, as the library recommends.
32
+ if var seed = try? Bytes.random(32) {
33
+ _ = secp256k1_context_randomize(ctx, seed)
34
+ seed.wipe()
35
+ }
36
+ return ctx
37
+ }()
38
+ private static let lock = NSLock()
39
+
40
+ private static func withContext<T>(_ body: (OpaquePointer) throws -> T) rethrows -> T {
41
+ lock.lock()
42
+ defer { lock.unlock() }
43
+ return try body(context)
44
+ }
45
+
46
+ static func isValidPrivateKey(_ key: [UInt8]) -> Bool {
47
+ key.count == 32 && withContext { secp256k1_ec_seckey_verify($0, key) == 1 }
48
+ }
49
+
50
+ /// Public key, 65-byte uncompressed (0x04 || x || y) or 33-byte compressed.
51
+ static func publicKey(of key: [UInt8], compressed: Bool) throws -> [UInt8] {
52
+ guard key.count == 32 else { throw WalletError(.internal, "invalid private key") }
53
+ return try withContext { ctx in
54
+ var pub = secp256k1_pubkey()
55
+ guard secp256k1_ec_pubkey_create(ctx, &pub, key) == 1 else { throw WalletError(.internal, "invalid private key") }
56
+ var out = [UInt8](repeating: 0, count: compressed ? 33 : 65)
57
+ var len = out.count
58
+ let flags = UInt32(compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED)
59
+ guard secp256k1_ec_pubkey_serialize(ctx, &out, &len, &pub, flags) == 1 else { throw WalletError(.internal) }
60
+ return Array(out[0..<len])
61
+ }
62
+ }
63
+
64
+ /// (key + tweak) mod n, for BIP32 child keys. Fails when tweak >= n or the result is zero.
65
+ static func addTweak(_ key: [UInt8], _ tweak: [UInt8]) throws -> [UInt8] {
66
+ var out = key
67
+ let ok = withContext { secp256k1_ec_seckey_tweak_add($0, &out, tweak) == 1 }
68
+ guard ok else {
69
+ out.wipe()
70
+ throw WalletError(.internal, "invalid child key")
71
+ }
72
+ return out
73
+ }
74
+
75
+ /// RFC 6979 deterministic, low-s ECDSA over a 32-byte hash.
76
+ static func sign(hash: [UInt8], key: [UInt8]) throws -> RecoverableSignature {
77
+ guard hash.count == 32, key.count == 32 else { throw WalletError(.internal, "invalid signing input") }
78
+ return try withContext { ctx in
79
+ var sig = secp256k1_ecdsa_recoverable_signature()
80
+ guard secp256k1_ecdsa_sign_recoverable(ctx, &sig, hash, key, nil, nil) == 1 else {
81
+ throw WalletError(.internal, "signing failed")
82
+ }
83
+ var compact = [UInt8](repeating: 0, count: 64)
84
+ var recid: Int32 = 0
85
+ secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, &compact, &recid, &sig)
86
+ return RecoverableSignature(r: Array(compact[0..<32]), s: Array(compact[32..<64]), recoveryId: Int(recid))
87
+ }
88
+ }
89
+
90
+ /// RFC 6979 deterministic, low-s ECDSA over a 32-byte hash, DER-encoded.
91
+ static func signDER(hash: [UInt8], key: [UInt8]) throws -> [UInt8] {
92
+ guard hash.count == 32, key.count == 32 else { throw WalletError(.internal, "invalid signing input") }
93
+ return try withContext { ctx in
94
+ var sig = secp256k1_ecdsa_signature()
95
+ guard secp256k1_ecdsa_sign(ctx, &sig, hash, key, nil, nil) == 1 else { throw WalletError(.internal, "signing failed") }
96
+ var out = [UInt8](repeating: 0, count: 72)
97
+ var len = out.count
98
+ guard secp256k1_ecdsa_signature_serialize_der(ctx, &out, &len, &sig) == 1 else { throw WalletError(.internal, "signing failed") }
99
+ return Array(out[0..<len])
100
+ }
101
+ }
102
+
103
+ /// Verifies a DER signature (tests and self-checks).
104
+ static func verifyDER(_ der: [UInt8], hash: [UInt8], publicKey: [UInt8]) -> Bool {
105
+ withContext { ctx in
106
+ var sig = secp256k1_ecdsa_signature()
107
+ var pub = secp256k1_pubkey()
108
+ guard secp256k1_ecdsa_signature_parse_der(ctx, &sig, der, der.count) == 1,
109
+ secp256k1_ec_pubkey_parse(ctx, &pub, publicKey, publicKey.count) == 1 else { return false }
110
+ return secp256k1_ecdsa_verify(ctx, &sig, hash, &pub) == 1
111
+ }
112
+ }
113
+
114
+ /// Recovers the uncompressed public key of a signature (used to verify proofs in tests and checks).
115
+ static func recover(hash: [UInt8], r: [UInt8], s: [UInt8], recoveryId: Int) -> [UInt8]? {
116
+ guard hash.count == 32, r.count == 32, s.count == 32, (0...3).contains(recoveryId) else { return nil }
117
+ return withContext { ctx in
118
+ var sig = secp256k1_ecdsa_recoverable_signature()
119
+ guard secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &sig, r + s, Int32(recoveryId)) == 1 else { return nil }
120
+ var pub = secp256k1_pubkey()
121
+ guard secp256k1_ecdsa_recover(ctx, &pub, &sig, hash) == 1 else { return nil }
122
+ var out = [UInt8](repeating: 0, count: 65)
123
+ var len = 65
124
+ secp256k1_ec_pubkey_serialize(ctx, &out, &len, &pub, UInt32(SECP256K1_EC_UNCOMPRESSED))
125
+ return out
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,123 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Crypto/Shamir.swift. Do not edit: change the source and run the script.
2
+ import Foundation
3
+
4
+ /// Shamir secret sharing over GF(2^8), byte-compatible with the `shamir-secret-sharing`
5
+ /// 0.0.4 package the web frame uses: polynomial x^8+x^4+x^3+x+1, generator 0xe5, and each
6
+ /// share is the y bytes followed by its x coordinate. Tables are copied from that package
7
+ /// (originally hashicorp/vault shamir/tables.go).
8
+ enum Shamir {
9
+ private static let logTable: [UInt8] = [
10
+ 0x00, 0xff, 0xc8, 0x08, 0x91, 0x10, 0xd0, 0x36, 0x5a, 0x3e, 0xd8, 0x43, 0x99, 0x77, 0xfe, 0x18,
11
+ 0x23, 0x20, 0x07, 0x70, 0xa1, 0x6c, 0x0c, 0x7f, 0x62, 0x8b, 0x40, 0x46, 0xc7, 0x4b, 0xe0, 0x0e,
12
+ 0xeb, 0x16, 0xe8, 0xad, 0xcf, 0xcd, 0x39, 0x53, 0x6a, 0x27, 0x35, 0x93, 0xd4, 0x4e, 0x48, 0xc3,
13
+ 0x2b, 0x79, 0x54, 0x28, 0x09, 0x78, 0x0f, 0x21, 0x90, 0x87, 0x14, 0x2a, 0xa9, 0x9c, 0xd6, 0x74,
14
+ 0xb4, 0x7c, 0xde, 0xed, 0xb1, 0x86, 0x76, 0xa4, 0x98, 0xe2, 0x96, 0x8f, 0x02, 0x32, 0x1c, 0xc1,
15
+ 0x33, 0xee, 0xef, 0x81, 0xfd, 0x30, 0x5c, 0x13, 0x9d, 0x29, 0x17, 0xc4, 0x11, 0x44, 0x8c, 0x80,
16
+ 0xf3, 0x73, 0x42, 0x1e, 0x1d, 0xb5, 0xf0, 0x12, 0xd1, 0x5b, 0x41, 0xa2, 0xd7, 0x2c, 0xe9, 0xd5,
17
+ 0x59, 0xcb, 0x50, 0xa8, 0xdc, 0xfc, 0xf2, 0x56, 0x72, 0xa6, 0x65, 0x2f, 0x9f, 0x9b, 0x3d, 0xba,
18
+ 0x7d, 0xc2, 0x45, 0x82, 0xa7, 0x57, 0xb6, 0xa3, 0x7a, 0x75, 0x4f, 0xae, 0x3f, 0x37, 0x6d, 0x47,
19
+ 0x61, 0xbe, 0xab, 0xd3, 0x5f, 0xb0, 0x58, 0xaf, 0xca, 0x5e, 0xfa, 0x85, 0xe4, 0x4d, 0x8a, 0x05,
20
+ 0xfb, 0x60, 0xb7, 0x7b, 0xb8, 0x26, 0x4a, 0x67, 0xc6, 0x1a, 0xf8, 0x69, 0x25, 0xb3, 0xdb, 0xbd,
21
+ 0x66, 0xdd, 0xf1, 0xd2, 0xdf, 0x03, 0x8d, 0x34, 0xd9, 0x92, 0x0d, 0x63, 0x55, 0xaa, 0x49, 0xec,
22
+ 0xbc, 0x95, 0x3c, 0x84, 0x0b, 0xf5, 0xe6, 0xe7, 0xe5, 0xac, 0x7e, 0x6e, 0xb9, 0xf9, 0xda, 0x8e,
23
+ 0x9a, 0xc9, 0x24, 0xe1, 0x0a, 0x15, 0x6b, 0x3a, 0xa0, 0x51, 0xf4, 0xea, 0xb2, 0x97, 0x9e, 0x5d,
24
+ 0x22, 0x88, 0x94, 0xce, 0x19, 0x01, 0x71, 0x4c, 0xa5, 0xe3, 0xc5, 0x31, 0xbb, 0xcc, 0x1f, 0x2d,
25
+ 0x3b, 0x52, 0x6f, 0xf6, 0x2e, 0x89, 0xf7, 0xc0, 0x68, 0x1b, 0x64, 0x04, 0x06, 0xbf, 0x83, 0x38,
26
+ ]
27
+
28
+ private static let expTable: [UInt8] = [
29
+ 0x01, 0xe5, 0x4c, 0xb5, 0xfb, 0x9f, 0xfc, 0x12, 0x03, 0x34, 0xd4, 0xc4, 0x16, 0xba, 0x1f, 0x36,
30
+ 0x05, 0x5c, 0x67, 0x57, 0x3a, 0xd5, 0x21, 0x5a, 0x0f, 0xe4, 0xa9, 0xf9, 0x4e, 0x64, 0x63, 0xee,
31
+ 0x11, 0x37, 0xe0, 0x10, 0xd2, 0xac, 0xa5, 0x29, 0x33, 0x59, 0x3b, 0x30, 0x6d, 0xef, 0xf4, 0x7b,
32
+ 0x55, 0xeb, 0x4d, 0x50, 0xb7, 0x2a, 0x07, 0x8d, 0xff, 0x26, 0xd7, 0xf0, 0xc2, 0x7e, 0x09, 0x8c,
33
+ 0x1a, 0x6a, 0x62, 0x0b, 0x5d, 0x82, 0x1b, 0x8f, 0x2e, 0xbe, 0xa6, 0x1d, 0xe7, 0x9d, 0x2d, 0x8a,
34
+ 0x72, 0xd9, 0xf1, 0x27, 0x32, 0xbc, 0x77, 0x85, 0x96, 0x70, 0x08, 0x69, 0x56, 0xdf, 0x99, 0x94,
35
+ 0xa1, 0x90, 0x18, 0xbb, 0xfa, 0x7a, 0xb0, 0xa7, 0xf8, 0xab, 0x28, 0xd6, 0x15, 0x8e, 0xcb, 0xf2,
36
+ 0x13, 0xe6, 0x78, 0x61, 0x3f, 0x89, 0x46, 0x0d, 0x35, 0x31, 0x88, 0xa3, 0x41, 0x80, 0xca, 0x17,
37
+ 0x5f, 0x53, 0x83, 0xfe, 0xc3, 0x9b, 0x45, 0x39, 0xe1, 0xf5, 0x9e, 0x19, 0x5e, 0xb6, 0xcf, 0x4b,
38
+ 0x38, 0x04, 0xb9, 0x2b, 0xe2, 0xc1, 0x4a, 0xdd, 0x48, 0x0c, 0xd0, 0x7d, 0x3d, 0x58, 0xde, 0x7c,
39
+ 0xd8, 0x14, 0x6b, 0x87, 0x47, 0xe8, 0x79, 0x84, 0x73, 0x3c, 0xbd, 0x92, 0xc9, 0x23, 0x8b, 0x97,
40
+ 0x95, 0x44, 0xdc, 0xad, 0x40, 0x65, 0x86, 0xa2, 0xa4, 0xcc, 0x7f, 0xec, 0xc0, 0xaf, 0x91, 0xfd,
41
+ 0xf7, 0x4f, 0x81, 0x2f, 0x5b, 0xea, 0xa8, 0x1c, 0x02, 0xd1, 0x98, 0x71, 0xed, 0x25, 0xe3, 0x24,
42
+ 0x06, 0x68, 0xb3, 0x93, 0x2c, 0x6f, 0x3e, 0x6c, 0x0a, 0xb8, 0xce, 0xae, 0x74, 0xb1, 0x42, 0xb4,
43
+ 0x1e, 0xd3, 0x49, 0xe9, 0x9c, 0xc8, 0xc6, 0xc7, 0x22, 0x6e, 0xdb, 0x20, 0xbf, 0x43, 0x51, 0x52,
44
+ 0x66, 0xb2, 0x76, 0x60, 0xda, 0xc5, 0xf3, 0xf6, 0xaa, 0xcd, 0x9a, 0xa0, 0x75, 0x54, 0x0e, 0x01,
45
+ ]
46
+
47
+ private static func div(_ a: UInt8, _ b: UInt8) throws -> UInt8 {
48
+ guard b != 0 else { throw WalletError(.internal, "cannot divide by zero") }
49
+ if a == 0 { return 0 }
50
+ let diff = (Int(logTable[Int(a)]) - Int(logTable[Int(b)]) + 255) % 255
51
+ return expTable[diff]
52
+ }
53
+
54
+ private static func mult(_ a: UInt8, _ b: UInt8) -> UInt8 {
55
+ if a == 0 || b == 0 { return 0 }
56
+ return expTable[(Int(logTable[Int(a)]) + Int(logTable[Int(b)])) % 255]
57
+ }
58
+
59
+ private static func interpolate(_ xs: [UInt8], _ ys: [UInt8], at x: UInt8) throws -> UInt8 {
60
+ var result: UInt8 = 0
61
+ for i in 0..<xs.count {
62
+ var basis: UInt8 = 1
63
+ for j in 0..<xs.count where i != j {
64
+ basis = mult(basis, try div(x ^ xs[j], xs[i] ^ xs[j]))
65
+ }
66
+ result ^= mult(ys[i], basis)
67
+ }
68
+ return result
69
+ }
70
+
71
+ private static func evaluate(_ coefficients: [UInt8], at x: UInt8) -> UInt8 {
72
+ var result = coefficients[coefficients.count - 1]
73
+ for c in coefficients.dropLast().reversed() { result = mult(result, x) ^ c }
74
+ return result
75
+ }
76
+
77
+ /// Same shuffle as the package's newCoordinates() (a permutation of 1...255).
78
+ private static func newCoordinates() throws -> [UInt8] {
79
+ var coordinates = (0..<255).map { UInt8($0 + 1) }
80
+ let randomIndices = try Bytes.random(255)
81
+ for i in 0..<255 {
82
+ let j = Int(randomIndices[i]) % 255
83
+ coordinates.swapAt(i, j)
84
+ }
85
+ return coordinates
86
+ }
87
+
88
+ static func split(_ secret: [UInt8], shares: Int, threshold: Int) throws -> [[UInt8]] {
89
+ guard !secret.isEmpty, (2...255).contains(shares), (2...255).contains(threshold), shares >= threshold else {
90
+ throw WalletError(.internal, "invalid split parameters")
91
+ }
92
+ let xs = try newCoordinates()
93
+ var result = (0..<shares).map { i -> [UInt8] in
94
+ var share = [UInt8](repeating: 0, count: secret.count + 1)
95
+ share[secret.count] = xs[i]
96
+ return share
97
+ }
98
+ for i in 0..<secret.count {
99
+ var coefficients = [secret[i]] + (try Bytes.random(threshold - 1))
100
+ for j in 0..<shares { result[j][i] = evaluate(coefficients, at: xs[j]) }
101
+ coefficients.wipe()
102
+ }
103
+ return result
104
+ }
105
+
106
+ static func combine(_ shares: [[UInt8]]) throws -> [UInt8] {
107
+ guard (2...255).contains(shares.count) else { throw WalletError(.internal, "need at least 2 shares") }
108
+ let length = shares[0].count
109
+ guard length >= 2, shares.allSatisfy({ $0.count == length }) else {
110
+ throw WalletError(.internal, "all shares must have the same byte length")
111
+ }
112
+ let xs = shares.map { $0[length - 1] }
113
+ guard Set(xs).count == xs.count else { throw WalletError(.internal, "duplicate share") }
114
+ var secret = [UInt8](repeating: 0, count: length - 1)
115
+ var ys = [UInt8](repeating: 0, count: shares.count)
116
+ defer { ys.wipe() }
117
+ for i in 0..<(length - 1) {
118
+ for j in 0..<shares.count { ys[j] = shares[j][i] }
119
+ secret[i] = try interpolate(xs, ys, at: 0)
120
+ }
121
+ return secret
122
+ }
123
+ }
@@ -0,0 +1,57 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/EVM/Address.swift. Do not edit: change the source and run the script.
2
+ import Foundation
3
+
4
+ enum EvmAddress {
5
+ /// Address of an uncompressed secp256k1 public key (0x04 || x || y), EIP-55 checksummed.
6
+ static func of(publicKey: [UInt8]) -> String {
7
+ let hash = Keccak.hash256(Array(publicKey.dropFirst()))
8
+ return checksum(Bytes.hex(Array(hash[12..<32])))
9
+ }
10
+
11
+ static func of(privateKey: [UInt8]) throws -> String {
12
+ of(publicKey: try Secp256k1.publicKey(of: privateKey, compressed: false))
13
+ }
14
+
15
+ /// EIP-55 for 40 hex digits (no prefix, any case). Same result as viem getAddress.
16
+ static func checksum(_ hex40: String) -> String {
17
+ let lower = hex40.lowercased()
18
+ let hash = Keccak.hash256(Array(lower.utf8))
19
+ var out = "0x"
20
+ for (i, c) in lower.enumerated() {
21
+ let nibble = (hash[i / 2] >> (i % 2 == 0 ? 4 : 0)) & 0x0f
22
+ out.append(c.isLetter && nibble >= 8 ? Character(c.uppercased()) : c)
23
+ }
24
+ return out
25
+ }
26
+
27
+ /// viem isAddress: 0x + 40 hex digits; with `strict`, mixed case must be a valid checksum.
28
+ static func isAddress(_ value: String, strict: Bool = true) -> Bool {
29
+ guard value.count == 42, value.hasPrefix("0x"), Bytes.isHex(value) else { return false }
30
+ let body = String(value.dropFirst(2))
31
+ if !strict || body == body.lowercased() || body == body.uppercased() { return true }
32
+ return checksum(body) == value
33
+ }
34
+
35
+ static func bytes(_ value: String) -> [UInt8]? {
36
+ guard value.count == 42, value.hasPrefix("0x") else { return nil }
37
+ return Bytes.fromHex(value)
38
+ }
39
+
40
+ /// viem getAddress for display; anything else is shown as given.
41
+ static func display(_ value: JSONValue?) -> String {
42
+ if case .string(let s) = value, isAddress(s, strict: false) { return checksum(String(s.dropFirst(2))) }
43
+ return value.map(jsString) ?? "undefined"
44
+ }
45
+
46
+ /// JavaScript String(value) for the cases the summaries print.
47
+ static func jsString(_ value: JSONValue) -> String {
48
+ switch value {
49
+ case .null: return "null"
50
+ case .bool(let b): return b ? "true" : "false"
51
+ case .number(let n): return n
52
+ case .string(let s): return s
53
+ case .array(let a): return a.map(jsString).joined(separator: ",")
54
+ case .object: return "[object Object]"
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,48 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/EVM/EvmAccount.swift. Do not edit: change the source and run the script.
2
+ import Foundation
3
+
4
+ /// A secp256k1 key in memory for the duration of one operation. Call `wipe()` when done.
5
+ final class EvmAccount {
6
+ private var key: [UInt8]
7
+ let address: String
8
+
9
+ init(privateKey: [UInt8]) throws {
10
+ key = privateKey
11
+ address = try EvmAddress.of(privateKey: privateKey)
12
+ }
13
+
14
+ deinit { key.wipe() }
15
+
16
+ func wipe() { key.wipe() }
17
+
18
+ /// r || s || v with v = 27/28 (viem serializeSignature).
19
+ private func signHash(_ hash: [UInt8]) throws -> String {
20
+ let sig = try Secp256k1.sign(hash: hash, key: key)
21
+ return Bytes.hex(sig.r + sig.s + [UInt8(27 + sig.recoveryId)], prefix: true)
22
+ }
23
+
24
+ enum Message {
25
+ case text(String)
26
+ case raw([UInt8])
27
+ }
28
+
29
+ static func hashMessage(_ message: Message) -> [UInt8] {
30
+ let body: [UInt8]
31
+ switch message {
32
+ case .text(let s): body = Array(s.utf8)
33
+ case .raw(let b): body = b
34
+ }
35
+ return Keccak.hash256(Array("\u{19}Ethereum Signed Message:\n\(body.count)".utf8) + body)
36
+ }
37
+
38
+ /// EIP-191 personal_sign.
39
+ func signMessage(_ message: Message) throws -> String { try signHash(EvmAccount.hashMessage(message)) }
40
+
41
+ func signTypedData(_ typedData: TypedData) throws -> String { try signHash(try typedData.digest()) }
42
+
43
+ /// Serialized signed transaction (0x-prefixed), as viem's account.signTransaction.
44
+ func signTransaction(_ tx: EvmTransaction) throws -> String {
45
+ let sig = try Secp256k1.sign(hash: Keccak.hash256(tx.serialized()), key: key)
46
+ return Bytes.hex(tx.serialized(signature: sig), prefix: true)
47
+ }
48
+ }
@@ -0,0 +1,165 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/EVM/Transaction.swift. Do not edit: change the source and run the script.
2
+ import Foundation
3
+
4
+ indirect enum RLPItem {
5
+ case bytes([UInt8])
6
+ case list([RLPItem])
7
+
8
+ func encoded() -> [UInt8] {
9
+ switch self {
10
+ case .bytes(let b):
11
+ if b.count == 1, b[0] < 0x80 { return b }
12
+ return RLPItem.header(b.count, offset: 0x80) + b
13
+ case .list(let items):
14
+ let body = items.flatMap { $0.encoded() }
15
+ return RLPItem.header(body.count, offset: 0xc0) + body
16
+ }
17
+ }
18
+
19
+ private static func header(_ length: Int, offset: UInt8) -> [UInt8] {
20
+ if length < 56 { return [offset + UInt8(length)] }
21
+ let len = BigUInt(UInt64(length)).bytes
22
+ return [offset + 55 + UInt8(len.count)] + len
23
+ }
24
+ }
25
+
26
+ /// An EVM transaction as viem serializes it (legacy with EIP-155, EIP-2930, EIP-1559).
27
+ struct EvmTransaction {
28
+ enum Kind: String { case legacy, eip2930, eip1559 }
29
+
30
+ struct AccessEntry {
31
+ var address: [UInt8]
32
+ var storageKeys: [[UInt8]]
33
+ }
34
+
35
+ var kind: Kind
36
+ var chainId: BigUInt
37
+ var nonce: BigUInt
38
+ var gas: BigUInt?
39
+ var gasPrice: BigUInt?
40
+ var maxFeePerGas: BigUInt?
41
+ var maxPriorityFeePerGas: BigUInt?
42
+ var to: [UInt8]?
43
+ var value: BigUInt?
44
+ var data: [UInt8]
45
+ var accessList: [AccessEntry]
46
+
47
+ private static let maxUint256 = BigUInt.power2(256) - BigUInt(1)
48
+
49
+ /// From the parsed request (see WalletOps.parseTransaction): numeric fields are already integers.
50
+ init(_ tx: JSONObject) throws {
51
+ func bad(_ m: String) -> WalletError { WalletError(.invalidRequest, m) }
52
+ func uint(_ name: String) throws -> BigUInt? {
53
+ guard let v = tx[name], v != .null else { return nil }
54
+ guard let n = v.jsBigInt, !n.negative else { throw bad("\(name) must be a non-negative integer") }
55
+ return n.magnitude
56
+ }
57
+ let maxFee = try uint("maxFeePerGas")
58
+ let maxPriority = try uint("maxPriorityFeePerGas")
59
+ let price = try uint("gasPrice")
60
+ let typeName: String
61
+ if let t = tx["type"], t.isTruthy {
62
+ guard case .string(let s) = t else { throw bad("unsupported transaction type") }
63
+ typeName = s
64
+ } else if tx["authorizationList"] != nil || tx["blobs"] != nil || tx["blobVersionedHashes"] != nil || tx["maxFeePerBlobGas"] != nil || tx["sidecars"] != nil {
65
+ throw bad("blob and EIP-7702 transactions are not supported")
66
+ } else if maxFee != nil || maxPriority != nil {
67
+ typeName = "eip1559"
68
+ } else if price != nil {
69
+ typeName = tx["accessList"] != nil ? "eip2930" : "legacy"
70
+ } else {
71
+ throw bad("cannot infer the transaction type")
72
+ }
73
+ if typeName == "eip4844" || typeName == "eip7702" { throw bad("blob and EIP-7702 transactions are not supported") }
74
+ // viem serializes any other type name as a legacy transaction.
75
+ kind = Kind(rawValue: typeName) ?? .legacy
76
+
77
+ guard let chain = try uint("chainId") else { throw bad("chainId required") }
78
+ chainId = chain
79
+ nonce = try uint("nonce") ?? BigUInt()
80
+ gas = try uint("gas")
81
+ gasPrice = price
82
+ maxFeePerGas = maxFee
83
+ maxPriorityFeePerGas = maxPriority
84
+ value = try uint("value")
85
+
86
+ if let t = tx["to"], t.isTruthy {
87
+ guard case .string(let s) = t, EvmAddress.isAddress(s), let b = EvmAddress.bytes(s) else { throw bad("invalid to address") }
88
+ to = b
89
+ } else {
90
+ to = nil
91
+ }
92
+ if let d = tx["data"], d != .null {
93
+ guard case .string(let s) = d, Bytes.isHex(s), let b = Bytes.fromHex(s) else { throw bad("data must be hex") }
94
+ data = b
95
+ } else {
96
+ data = []
97
+ }
98
+ var list = [AccessEntry]()
99
+ if kind != .legacy, let raw = tx["accessList"], raw != .null {
100
+ guard let entries = raw.arrayValue else { throw bad("accessList must be an array") }
101
+ for e in entries {
102
+ guard case .string(let a) = e["address"] ?? .null, EvmAddress.isAddress(a, strict: false), let ab = EvmAddress.bytes(a),
103
+ let keys = e["storageKeys"]?.arrayValue else { throw bad("invalid accessList entry") }
104
+ var sk = [[UInt8]]()
105
+ for k in keys {
106
+ guard case .string(let ks) = k, ks.count == 66, Bytes.isHex(ks), let kb = Bytes.fromHex(ks) else {
107
+ throw bad("storage keys must be 32 bytes")
108
+ }
109
+ sk.append(kb)
110
+ }
111
+ list.append(AccessEntry(address: ab, storageKeys: sk))
112
+ }
113
+ }
114
+ accessList = list
115
+
116
+ switch kind {
117
+ case .eip1559:
118
+ guard !chainId.isZero else { throw bad("invalid chainId") }
119
+ if let f = maxFeePerGas, f > EvmTransaction.maxUint256 { throw bad("maxFeePerGas too high") }
120
+ if let p = maxPriorityFeePerGas, let f = maxFeePerGas, !p.isZero, !f.isZero, p > f { throw bad("maxPriorityFeePerGas above maxFeePerGas") }
121
+ case .eip2930:
122
+ guard !chainId.isZero else { throw bad("invalid chainId") }
123
+ if maxFeePerGas?.isZero == false || maxPriorityFeePerGas?.isZero == false { throw bad("maxFeePerGas is not an EIP-2930 field") }
124
+ if let p = gasPrice, p > EvmTransaction.maxUint256 { throw bad("gasPrice too high") }
125
+ case .legacy:
126
+ guard !chainId.isZero else { throw bad("invalid chainId") }
127
+ if maxFeePerGas?.isZero == false || maxPriorityFeePerGas?.isZero == false { throw bad("maxFeePerGas is not a legacy field") }
128
+ if let p = gasPrice, p > EvmTransaction.maxUint256 { throw bad("gasPrice too high") }
129
+ }
130
+ }
131
+
132
+ private func num(_ v: BigUInt?) -> RLPItem { .bytes(v?.bytes ?? []) }
133
+
134
+ private var accessListItem: RLPItem {
135
+ .list(accessList.map { .list([.bytes($0.address), .list($0.storageKeys.map { .bytes($0) })]) })
136
+ }
137
+
138
+ /// viem serializeTransaction(tx, signature?).
139
+ func serialized(signature: Secp256k1.RecoverableSignature? = nil) -> [UInt8] {
140
+ let trim: ([UInt8]) -> [UInt8] = { BigUInt(bytes: $0).bytes }
141
+ switch kind {
142
+ case .legacy:
143
+ var fields: [RLPItem] = [num(nonce), num(gasPrice), num(gas), .bytes(to ?? []), num(value), .bytes(data)]
144
+ if let sig = signature {
145
+ let v = chainId * BigUInt(2) + BigUInt(UInt64(35 + sig.recoveryId))
146
+ fields += [num(v), .bytes(trim(sig.r)), .bytes(trim(sig.s))]
147
+ } else {
148
+ fields += [num(chainId), .bytes([]), .bytes([])]
149
+ }
150
+ return RLPItem.list(fields).encoded()
151
+ case .eip2930, .eip1559:
152
+ var fields: [RLPItem] = [num(chainId), num(nonce)]
153
+ if kind == .eip1559 {
154
+ fields += [num(maxPriorityFeePerGas), num(maxFeePerGas)]
155
+ } else {
156
+ fields += [num(gasPrice)]
157
+ }
158
+ fields += [num(gas), .bytes(to ?? []), num(value), .bytes(data), accessListItem]
159
+ if let sig = signature {
160
+ fields += [.bytes(sig.recoveryId == 1 ? [1] : []), .bytes(trim(sig.r)), .bytes(trim(sig.s))]
161
+ }
162
+ return [kind == .eip1559 ? 0x02 : 0x01] + RLPItem.list(fields).encoded()
163
+ }
164
+ }
165
+ }