@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,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
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/EVM/TypedData.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// EIP-712 v4 hashing with viem's rules (hashTypedData + validateTypedData).
|
|
5
|
+
struct TypedData {
|
|
6
|
+
struct Field {
|
|
7
|
+
var name: String
|
|
8
|
+
var type: String
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var domain: JSONObject
|
|
12
|
+
var types: [String: [Field]]
|
|
13
|
+
var primaryType: String
|
|
14
|
+
var message: JSONValue
|
|
15
|
+
|
|
16
|
+
private static func bad(_ m: String) -> WalletError { WalletError(.invalidRequest, m) }
|
|
17
|
+
|
|
18
|
+
init(_ value: JSONValue) throws {
|
|
19
|
+
guard let obj = value.objectValue else { throw TypedData.bad("typedData must be an object") }
|
|
20
|
+
let domainValue = obj["domain"] ?? .null
|
|
21
|
+
if domainValue == .null {
|
|
22
|
+
domain = JSONObject()
|
|
23
|
+
} else {
|
|
24
|
+
guard let d = domainValue.objectValue else { throw TypedData.bad("domain must be an object") }
|
|
25
|
+
domain = d
|
|
26
|
+
}
|
|
27
|
+
guard case .string(let primary) = obj["primaryType"] ?? .null else { throw TypedData.bad("primaryType must be a string") }
|
|
28
|
+
primaryType = primary
|
|
29
|
+
message = obj["message"] ?? .null
|
|
30
|
+
guard let rawTypes = obj["types"]?.objectValue else { throw TypedData.bad("types must be an object") }
|
|
31
|
+
var parsed = [String: [Field]]()
|
|
32
|
+
parsed["EIP712Domain"] = TypedData.domainFields(domain)
|
|
33
|
+
for (name, list) in rawTypes.entries {
|
|
34
|
+
guard let arr = list.arrayValue else { throw TypedData.bad("types.\(name) must be an array") }
|
|
35
|
+
parsed[name] = try arr.map { f in
|
|
36
|
+
guard case .string(let n) = f["name"] ?? .null, case .string(let t) = f["type"] ?? .null else {
|
|
37
|
+
throw TypedData.bad("invalid field in \(name)")
|
|
38
|
+
}
|
|
39
|
+
return Field(name: n, type: t)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
types = parsed
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// getTypesForEIP712Domain: chainId only when it is a number, the others when truthy.
|
|
46
|
+
private static func domainFields(_ d: JSONObject) -> [Field] {
|
|
47
|
+
var out = [Field]()
|
|
48
|
+
if case .string = d["name"] ?? .null { out.append(Field(name: "name", type: "string")) }
|
|
49
|
+
if d["version"]?.isTruthy == true { out.append(Field(name: "version", type: "string")) }
|
|
50
|
+
if case .number = d["chainId"] ?? .null { out.append(Field(name: "chainId", type: "uint256")) }
|
|
51
|
+
if d["verifyingContract"]?.isTruthy == true { out.append(Field(name: "verifyingContract", type: "address")) }
|
|
52
|
+
if d["salt"]?.isTruthy == true { out.append(Field(name: "salt", type: "bytes32")) }
|
|
53
|
+
return out
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func digest() throws -> [UInt8] {
|
|
57
|
+
try validate()
|
|
58
|
+
var parts: [UInt8] = [0x19, 0x01]
|
|
59
|
+
parts += try hashStruct("EIP712Domain", .object(domain))
|
|
60
|
+
if primaryType != "EIP712Domain" { parts += try hashStruct(primaryType, message) }
|
|
61
|
+
return Keccak.hash256(parts)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MARK: - Validation (validateTypedData)
|
|
65
|
+
|
|
66
|
+
private func validate() throws {
|
|
67
|
+
func validateData(_ fields: [Field], _ data: JSONValue) throws {
|
|
68
|
+
for f in fields {
|
|
69
|
+
let value = data[f.name]
|
|
70
|
+
let base = f.type.replacingOccurrences(of: #"(\[[0-9]*\])+$"#, with: "", options: .regularExpression)
|
|
71
|
+
if base == "int" || base == "uint" { throw TypedData.bad("invalid type \(f.type)") }
|
|
72
|
+
if let (signed, bits) = TypedData.integerType(f.type), let v = value, case .number = v {
|
|
73
|
+
guard let n = v.jsBigInt, n.fits(bits: bits, signed: signed) else { throw TypedData.bad("\(f.name) out of range") }
|
|
74
|
+
}
|
|
75
|
+
if f.type == "address", case .string(let s) = value ?? .null, !EvmAddress.isAddress(s) {
|
|
76
|
+
throw TypedData.bad("invalid address \(s)")
|
|
77
|
+
}
|
|
78
|
+
if let size = TypedData.fixedBytesSize(f.type), size > 0 {
|
|
79
|
+
guard case .string(let s) = value ?? .null, let b = Bytes.fromHex(s), Bytes.isHex(s), b.count == size else {
|
|
80
|
+
throw TypedData.bad("\(f.name) must be \(size) bytes")
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if let nested = types[f.type] {
|
|
84
|
+
guard let v = value else { throw TypedData.bad("missing \(f.name)") }
|
|
85
|
+
try validateData(nested, v)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if let d = types["EIP712Domain"] { try validateData(d, .object(domain)) }
|
|
90
|
+
if primaryType != "EIP712Domain" {
|
|
91
|
+
guard let fields = types[primaryType] else { throw TypedData.bad("unknown primaryType \(primaryType)") }
|
|
92
|
+
try validateData(fields, message)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// uint8..uint256 / int8..int256 in steps of 8.
|
|
97
|
+
static func integerType(_ type: String) -> (Bool, Int)? {
|
|
98
|
+
let signed = type.hasPrefix("int")
|
|
99
|
+
guard signed || type.hasPrefix("uint") else { return nil }
|
|
100
|
+
let digits = type.dropFirst(signed ? 3 : 4)
|
|
101
|
+
guard let bits = Int(digits), String(bits) == digits, bits >= 8, bits <= 256, bits % 8 == 0 else { return nil }
|
|
102
|
+
return (signed, bits)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// bytes1..bytes32 (0 for dynamic `bytes`, nil otherwise).
|
|
106
|
+
static func fixedBytesSize(_ type: String) -> Int? {
|
|
107
|
+
guard type.hasPrefix("bytes") else { return nil }
|
|
108
|
+
let digits = type.dropFirst(5)
|
|
109
|
+
if digits.isEmpty { return 0 }
|
|
110
|
+
guard let n = Int(digits), String(n) == digits, n >= 1, n <= 32 else { return nil }
|
|
111
|
+
return n
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - Encoding
|
|
115
|
+
|
|
116
|
+
func encodeType(_ primary: String) -> String {
|
|
117
|
+
var deps = Set<String>()
|
|
118
|
+
findDependencies(primary, &deps)
|
|
119
|
+
deps.remove(primary)
|
|
120
|
+
let sorted = deps.sorted { $0.utf16.lexicographicallyPrecedes($1.utf16) }
|
|
121
|
+
return ([primary] + sorted).map { t in
|
|
122
|
+
"\(t)(" + (types[t] ?? []).map { "\($0.type) \($0.name)" }.joined(separator: ",") + ")"
|
|
123
|
+
}.joined()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private func findDependencies(_ type: String, _ results: inout Set<String>) {
|
|
127
|
+
let base = String(type.prefix { $0.isLetter || $0.isNumber || $0 == "_" })
|
|
128
|
+
guard !results.contains(base), let fields = types[base] else { return }
|
|
129
|
+
results.insert(base)
|
|
130
|
+
for f in fields { findDependencies(f.type, &results) }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func hashStruct(_ type: String, _ data: JSONValue) throws -> [UInt8] {
|
|
134
|
+
Keccak.hash256(try encodeData(type, data))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private func encodeData(_ type: String, _ data: JSONValue) throws -> [UInt8] {
|
|
138
|
+
guard let fields = types[type] else { throw TypedData.bad("unknown type \(type)") }
|
|
139
|
+
guard case .object = data else { throw TypedData.bad("\(type) value must be an object") }
|
|
140
|
+
var out = Keccak.hash256(Array(encodeType(type).utf8))
|
|
141
|
+
for f in fields { out += try encodeField(f.type, data[f.name]) }
|
|
142
|
+
return out
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// One 32-byte word per field.
|
|
146
|
+
private func encodeField(_ type: String, _ value: JSONValue?) throws -> [UInt8] {
|
|
147
|
+
if types[type] != nil {
|
|
148
|
+
guard let v = value else { throw TypedData.bad("missing \(type) value") }
|
|
149
|
+
return Keccak.hash256(try encodeData(type, v))
|
|
150
|
+
}
|
|
151
|
+
if type == "bytes" {
|
|
152
|
+
guard case .string(let s) = value ?? .null, Bytes.isHex(s), let b = Bytes.fromHex(s) else { throw TypedData.bad("bytes must be hex") }
|
|
153
|
+
return Keccak.hash256(b)
|
|
154
|
+
}
|
|
155
|
+
if type == "string" {
|
|
156
|
+
guard case .string(let s) = value ?? .null else { throw TypedData.bad("string expected") }
|
|
157
|
+
return Keccak.hash256(Array(s.utf8))
|
|
158
|
+
}
|
|
159
|
+
if type.hasSuffix("]"), let open = type.range(of: "[", options: .backwards) {
|
|
160
|
+
let inner = String(type[..<open.lowerBound])
|
|
161
|
+
guard let items = value?.arrayValue else { throw TypedData.bad("array expected for \(type)") }
|
|
162
|
+
var concat = [UInt8]()
|
|
163
|
+
for item in items { concat += try encodeField(inner, item) }
|
|
164
|
+
return Keccak.hash256(concat)
|
|
165
|
+
}
|
|
166
|
+
return try encodeAtomic(type, value)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private func encodeAtomic(_ type: String, _ value: JSONValue?) throws -> [UInt8] {
|
|
170
|
+
let v = value ?? .null
|
|
171
|
+
if type == "address" {
|
|
172
|
+
guard case .string(let s) = v, EvmAddress.isAddress(s), let b = EvmAddress.bytes(s) else { throw TypedData.bad("invalid address") }
|
|
173
|
+
return [UInt8](repeating: 0, count: 12) + b
|
|
174
|
+
}
|
|
175
|
+
if type == "bool" {
|
|
176
|
+
guard case .bool(let b) = v else { throw TypedData.bad("bool expected") }
|
|
177
|
+
return [UInt8](repeating: 0, count: 31) + [b ? 1 : 0]
|
|
178
|
+
}
|
|
179
|
+
if let (signed, bits) = TypedData.integerType(type) {
|
|
180
|
+
guard let n = v.jsBigInt, n.fits(bits: bits, signed: signed), let word = n.word() else {
|
|
181
|
+
throw TypedData.bad("\(type) value out of range")
|
|
182
|
+
}
|
|
183
|
+
return word
|
|
184
|
+
}
|
|
185
|
+
if let size = TypedData.fixedBytesSize(type), size > 0 {
|
|
186
|
+
guard case .string(let s) = v, Bytes.isHex(s), let b = Bytes.fromHex(s), b.count == size else {
|
|
187
|
+
throw TypedData.bad("\(type) size mismatch")
|
|
188
|
+
}
|
|
189
|
+
return b + [UInt8](repeating: 0, count: 32 - size)
|
|
190
|
+
}
|
|
191
|
+
throw TypedData.bad("unsupported type \(type)")
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Support/BigInteger.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Unsigned arbitrary-precision integer for amounts, gas and EIP-712 integers. Only the
|
|
5
|
+
/// operations the wallet needs (parsing, printing, encoding, add/sub/mul, compare).
|
|
6
|
+
struct BigUInt: Comparable, Hashable, CustomStringConvertible {
|
|
7
|
+
/// Little-endian 32-bit limbs without trailing zero limbs.
|
|
8
|
+
private(set) var limbs: [UInt32]
|
|
9
|
+
|
|
10
|
+
init(_ value: UInt64 = 0) {
|
|
11
|
+
limbs = [UInt32(truncatingIfNeeded: value), UInt32(truncatingIfNeeded: value >> 32)]
|
|
12
|
+
normalize()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
init(limbs: [UInt32]) {
|
|
16
|
+
self.limbs = limbs
|
|
17
|
+
normalize()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Big-endian bytes.
|
|
21
|
+
init(bytes: [UInt8]) {
|
|
22
|
+
var out = [UInt32]()
|
|
23
|
+
var i = bytes.count
|
|
24
|
+
while i > 0 {
|
|
25
|
+
var limb: UInt32 = 0
|
|
26
|
+
let start = Swift.max(0, i - 4)
|
|
27
|
+
for b in bytes[start..<i] { limb = limb << 8 | UInt32(b) }
|
|
28
|
+
out.append(limb)
|
|
29
|
+
i = start
|
|
30
|
+
}
|
|
31
|
+
self.init(limbs: out)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
init?(decimal text: String) {
|
|
35
|
+
guard !text.isEmpty, text.utf8.allSatisfy({ $0 >= 0x30 && $0 <= 0x39 }) else { return nil }
|
|
36
|
+
var v = BigUInt()
|
|
37
|
+
for c in text.utf8 { v = v.multiplied(by: 10).adding(UInt32(c - 0x30)) }
|
|
38
|
+
self = v
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
init?(hexDigits text: String) {
|
|
42
|
+
guard !text.isEmpty, !text.contains("x"), !text.contains("X"), let bytes = Bytes.fromHex(text) else { return nil }
|
|
43
|
+
self.init(bytes: bytes)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private mutating func normalize() {
|
|
47
|
+
while let last = limbs.last, last == 0 { limbs.removeLast() }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var isZero: Bool { limbs.isEmpty }
|
|
51
|
+
|
|
52
|
+
var bitWidth: Int {
|
|
53
|
+
guard let last = limbs.last else { return 0 }
|
|
54
|
+
return (limbs.count - 1) * 32 + (32 - last.leadingZeroBitCount)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Minimal big-endian bytes; zero is empty.
|
|
58
|
+
var bytes: [UInt8] {
|
|
59
|
+
var out = [UInt8]()
|
|
60
|
+
for limb in limbs.reversed() {
|
|
61
|
+
out += [UInt8(limb >> 24), UInt8(truncatingIfNeeded: limb >> 16), UInt8(truncatingIfNeeded: limb >> 8), UInt8(truncatingIfNeeded: limb)]
|
|
62
|
+
}
|
|
63
|
+
if let first = out.firstIndex(where: { $0 != 0 }) { return Array(out[first...]) }
|
|
64
|
+
return []
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Big-endian, left-padded to `size` bytes; nil when it does not fit.
|
|
68
|
+
func padded(to size: Int) -> [UInt8]? {
|
|
69
|
+
let b = bytes
|
|
70
|
+
guard b.count <= size else { return nil }
|
|
71
|
+
return [UInt8](repeating: 0, count: size - b.count) + b
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var uint64: UInt64? {
|
|
75
|
+
guard limbs.count <= 2 else { return nil }
|
|
76
|
+
return limbs.enumerated().reduce(UInt64(0)) { $0 | UInt64($1.element) << (32 * UInt64($1.offset)) }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static func power2(_ n: Int) -> BigUInt {
|
|
80
|
+
var l = [UInt32](repeating: 0, count: n / 32 + 1)
|
|
81
|
+
l[n / 32] = 1 << UInt32(n % 32)
|
|
82
|
+
return BigUInt(limbs: l)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func adding(_ small: UInt32) -> BigUInt { self + BigUInt(UInt64(small)) }
|
|
86
|
+
|
|
87
|
+
func multiplied(by small: UInt32) -> BigUInt { self * BigUInt(UInt64(small)) }
|
|
88
|
+
|
|
89
|
+
static func + (a: BigUInt, b: BigUInt) -> BigUInt {
|
|
90
|
+
var out = [UInt32]()
|
|
91
|
+
var carry: UInt64 = 0
|
|
92
|
+
for i in 0..<Swift.max(a.limbs.count, b.limbs.count) {
|
|
93
|
+
let s = UInt64(i < a.limbs.count ? a.limbs[i] : 0) + UInt64(i < b.limbs.count ? b.limbs[i] : 0) + carry
|
|
94
|
+
out.append(UInt32(truncatingIfNeeded: s))
|
|
95
|
+
carry = s >> 32
|
|
96
|
+
}
|
|
97
|
+
if carry > 0 { out.append(UInt32(carry)) }
|
|
98
|
+
return BigUInt(limbs: out)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// a - b for a >= b.
|
|
102
|
+
static func - (a: BigUInt, b: BigUInt) -> BigUInt {
|
|
103
|
+
precondition(a >= b)
|
|
104
|
+
var out = [UInt32]()
|
|
105
|
+
var borrow: Int64 = 0
|
|
106
|
+
for i in 0..<a.limbs.count {
|
|
107
|
+
var d = Int64(a.limbs[i]) - Int64(i < b.limbs.count ? b.limbs[i] : 0) - borrow
|
|
108
|
+
borrow = 0
|
|
109
|
+
if d < 0 { d += 1 << 32; borrow = 1 }
|
|
110
|
+
out.append(UInt32(d))
|
|
111
|
+
}
|
|
112
|
+
return BigUInt(limbs: out)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static func * (a: BigUInt, b: BigUInt) -> BigUInt {
|
|
116
|
+
if a.isZero || b.isZero { return BigUInt() }
|
|
117
|
+
var out = [UInt32](repeating: 0, count: a.limbs.count + b.limbs.count)
|
|
118
|
+
for i in 0..<a.limbs.count {
|
|
119
|
+
var carry: UInt64 = 0
|
|
120
|
+
for j in 0..<b.limbs.count {
|
|
121
|
+
let t = UInt64(a.limbs[i]) * UInt64(b.limbs[j]) + UInt64(out[i + j]) + carry
|
|
122
|
+
out[i + j] = UInt32(truncatingIfNeeded: t)
|
|
123
|
+
carry = t >> 32
|
|
124
|
+
}
|
|
125
|
+
out[i + b.limbs.count] = UInt32(carry)
|
|
126
|
+
}
|
|
127
|
+
return BigUInt(limbs: out)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func quotientAndRemainder(dividingBy d: UInt32) -> (BigUInt, UInt32) {
|
|
131
|
+
var q = [UInt32](repeating: 0, count: limbs.count)
|
|
132
|
+
var rem: UInt64 = 0
|
|
133
|
+
for i in stride(from: limbs.count - 1, through: 0, by: -1) {
|
|
134
|
+
let cur = rem << 32 | UInt64(limbs[i])
|
|
135
|
+
q[i] = UInt32(cur / UInt64(d))
|
|
136
|
+
rem = cur % UInt64(d)
|
|
137
|
+
}
|
|
138
|
+
return (BigUInt(limbs: q), UInt32(rem))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
var description: String {
|
|
142
|
+
if isZero { return "0" }
|
|
143
|
+
var parts = [String]()
|
|
144
|
+
var v = self
|
|
145
|
+
while !v.isZero {
|
|
146
|
+
let (q, r) = v.quotientAndRemainder(dividingBy: 1_000_000_000)
|
|
147
|
+
parts.append(String(r))
|
|
148
|
+
v = q
|
|
149
|
+
}
|
|
150
|
+
var out = parts.removeLast()
|
|
151
|
+
for p in parts.reversed() { out += String(repeating: "0", count: 9 - p.count) + p }
|
|
152
|
+
return out
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static func < (a: BigUInt, b: BigUInt) -> Bool {
|
|
156
|
+
if a.limbs.count != b.limbs.count { return a.limbs.count < b.limbs.count }
|
|
157
|
+
for i in stride(from: a.limbs.count - 1, through: 0, by: -1) where a.limbs[i] != b.limbs[i] {
|
|
158
|
+
return a.limbs[i] < b.limbs[i]
|
|
159
|
+
}
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Signed integer with JavaScript `BigInt()` parsing semantics.
|
|
165
|
+
struct BigInteger: Equatable, CustomStringConvertible {
|
|
166
|
+
var negative: Bool
|
|
167
|
+
var magnitude: BigUInt
|
|
168
|
+
|
|
169
|
+
init(_ magnitude: BigUInt, negative: Bool = false) {
|
|
170
|
+
self.magnitude = magnitude
|
|
171
|
+
self.negative = negative && !magnitude.isZero
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// `BigInt(string)`: surrounding whitespace ignored, empty is 0, 0x/0o/0b prefixes, optional sign for decimals.
|
|
175
|
+
init?(jsString raw: String) {
|
|
176
|
+
let s = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
177
|
+
if s.isEmpty { self.init(BigUInt()); return }
|
|
178
|
+
let lower = s.lowercased()
|
|
179
|
+
if lower.hasPrefix("0x") {
|
|
180
|
+
guard let v = BigUInt(hexDigits: String(s.dropFirst(2))) else { return nil }
|
|
181
|
+
self.init(v)
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
if lower.hasPrefix("0o") || lower.hasPrefix("0b") {
|
|
185
|
+
let radix: UInt32 = lower.hasPrefix("0o") ? 8 : 2
|
|
186
|
+
let digits = s.dropFirst(2)
|
|
187
|
+
guard !digits.isEmpty else { return nil }
|
|
188
|
+
var v = BigUInt()
|
|
189
|
+
for c in digits.utf8 {
|
|
190
|
+
let d = UInt32(c) &- 0x30
|
|
191
|
+
guard d < radix else { return nil }
|
|
192
|
+
v = v.multiplied(by: radix).adding(d)
|
|
193
|
+
}
|
|
194
|
+
self.init(v)
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
var body = Substring(s)
|
|
198
|
+
var neg = false
|
|
199
|
+
if body.hasPrefix("-") { neg = true; body = body.dropFirst() } else if body.hasPrefix("+") { body = body.dropFirst() }
|
|
200
|
+
guard let v = BigUInt(decimal: String(body)) else { return nil }
|
|
201
|
+
self.init(v, negative: neg)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
var description: String { (negative ? "-" : "") + magnitude.description }
|
|
205
|
+
|
|
206
|
+
/// 32-byte two's complement.
|
|
207
|
+
func word() -> [UInt8]? {
|
|
208
|
+
if !negative { return magnitude.padded(to: 32) }
|
|
209
|
+
let mod = BigUInt.power2(256)
|
|
210
|
+
guard magnitude <= BigUInt.power2(255) else { return nil }
|
|
211
|
+
return (mod - magnitude).padded(to: 32)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Range check for uintN / intN.
|
|
215
|
+
func fits(bits: Int, signed: Bool) -> Bool {
|
|
216
|
+
if !signed { return !negative && magnitude.bitWidth <= bits }
|
|
217
|
+
if negative { return magnitude <= BigUInt.power2(bits - 1) }
|
|
218
|
+
return magnitude < BigUInt.power2(bits - 1)
|
|
219
|
+
}
|
|
220
|
+
}
|