@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.
- package/CHANGELOG.md +20 -0
- package/README.md +251 -2
- package/android/build.gradle +40 -0
- package/android/consumer-rules.pro +10 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +235 -0
- package/android/src/vendor/GENERATED.md +4 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AndroidCapabilityProbe.kt +86 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +218 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bitcoin.kt +296 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Chains.kt +157 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Der.kt +17 -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/Encodings.kt +133 -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 +86 -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 +236 -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/Solana.kt +195 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Tron.kt +184 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +361 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletCapabilities.kt +91 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +74 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +551 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +78 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Xrp.kt +193 -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 +92 -0
- package/build/client.d.ts.map +1 -0
- package/build/client.js +431 -0
- package/build/client.js.map +1 -0
- package/build/errors.d.ts +25 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +15 -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 +43 -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 +167 -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 +41 -0
- package/ios/AuthonWalletModule.swift +233 -0
- package/ios/vendor/AuthonWallet/AuthonWallet.swift +232 -0
- package/ios/vendor/AuthonWallet/Capabilities.swift +149 -0
- package/ios/vendor/AuthonWallet/Chains/Bitcoin.swift +296 -0
- package/ios/vendor/AuthonWallet/Chains/Solana.swift +207 -0
- package/ios/vendor/AuthonWallet/Chains/Tron.swift +181 -0
- package/ios/vendor/AuthonWallet/Chains/XRP.swift +191 -0
- package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
- package/ios/vendor/AuthonWallet/Crypto/Chains.swift +278 -0
- package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
- package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +128 -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 +361 -0
- package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +654 -0
- package/ios/vendor/AuthonWallet/WalletError.swift +84 -0
- package/ios/vendor/CKeccak/LICENSE +22 -0
- package/ios/vendor/CKeccak/README.md +30 -0
- package/ios/vendor/CKeccak/byte_order.h +59 -0
- package/ios/vendor/CKeccak/include/ckeccak.h +13 -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/ripemd160.c +343 -0
- package/ios/vendor/CKeccak/ripemd160.h +22 -0
- package/ios/vendor/CKeccak/sha3.c +413 -0
- package/ios/vendor/CKeccak/sha3.h +89 -0
- package/ios/vendor/CMonocypher/LICENCE.md +173 -0
- package/ios/vendor/CMonocypher/README.md +27 -0
- package/ios/vendor/CMonocypher/include/monocypher-ed25519.h +140 -0
- package/ios/vendor/CMonocypher/include/monocypher.h +321 -0
- package/ios/vendor/CMonocypher/monocypher-ed25519.c +500 -0
- package/ios/vendor/CMonocypher/monocypher.c +2956 -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 +66 -3
- package/src/authonContext.ts +30 -0
- package/src/client.ts +459 -0
- package/src/errors.ts +58 -0
- package/src/index.ts +26 -0
- package/src/native.ts +52 -0
- package/src/types.ts +171 -0
- 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
|
+
}
|