Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f15701f
Update README.md
Kushmanmb Dec 24, 2025
79429d6
Update README.md
Kushmanmb Dec 27, 2025
bcfa3fc
Merge pull request #1 from Kushmanmb/Kushmanmb-patch-1
Kushmanmb Dec 27, 2025
01570f6
Initial plan
Copilot Dec 29, 2025
c4cdf4c
Refactor duplicated code in Swift files
Copilot Dec 29, 2025
95acbd0
Remove redundant nil check in createBIP32Keystore
Copilot Dec 29, 2025
85993a2
Initial plan
Copilot Dec 29, 2025
d4f1b3f
Optimize slow and inefficient code patterns
Copilot Dec 29, 2025
3dfe4e4
Address code review feedback
Copilot Dec 29, 2025
02481d5
Fix edge case handling to preserve original behavior
Copilot Dec 29, 2025
073b69f
Merge pull request #3 from Kushmanmb/copilot/improve-slow-code-effici…
Kushmanmb Jan 1, 2026
2938dde
Update README.md
Kushmanmb Jan 1, 2026
7fa0402
Update README.md
Kushmanmb Jan 1, 2026
e8115e4
Merge pull request #2 from Kushmanmb/copilot/refactor-duplicate-code
Kushmanmb Jan 3, 2026
2106109
Initial plan
Copilot Jan 7, 2026
1111c2b
Add .gitattributes and improve .gitignore for iOS development
Copilot Jan 7, 2026
5a07efb
Fix duplicate *.ipa entry and treat SVG as text files
Copilot Jan 7, 2026
5e8937b
kushmanmb
Kushmanmb Jan 7, 2026
a26316a
Fix README.md - remove corrupted text and fix formatting
Kushmanmb Jan 16, 2026
a62f02a
Initial plan
Copilot Feb 4, 2026
15167b3
Refactor duplicated code in Web3Wrapper and CheckboxButton
Copilot Feb 4, 2026
4c1566d
Fix security issue with defer in extractPrivateKey and simplify encod…
Copilot Feb 4, 2026
5e60ae7
Add security documentation to extractPrivateKey method
Copilot Feb 4, 2026
b9c5144
Merge pull request #5 from Kushmanmb/copilot/refactor-duplicate-code
Kushmanmb Feb 4, 2026
ffeb5ef
Initial plan
Copilot Mar 28, 2026
25a565f
Update pod dependencies to current versions
Copilot Mar 28, 2026
ca53e84
Merge pull request #6 from kushmanmb-org/copilot/update-pod-dependencies
Kushmanmb Mar 28, 2026
55596a8
fix: audit bug fixes - strongify semicolon, dispatch_sync, weak self …
Copilot Apr 3, 2026
ee2c3f3
Merge pull request #7 from kushmanmb-org/copilot/fix-bugs-and-code-er…
Kushmanmb Apr 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Auto detect text files and perform LF normalization
* text=auto

# Source code
*.swift text diff=swift
*.h text diff=objc
*.m text diff=objc
*.mm text diff=objc
*.c text diff=c
*.cpp text diff=cpp

# Xcode project files
*.pbxproj text merge=union
*.xcworkspacedata text merge=union
*.xcscheme text
*.xcconfig text

# Property lists
*.plist text
*.strings text
*.storyboard text
*.xib text

# Scripts
*.sh text eol=lf

# Documentation
*.md text
*.txt text

# Images
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.svg text

# Fonts
*.ttf binary
*.otf binary

# Archives
*.zip binary
*.tar binary
*.gz binary

# Other
*.pdf binary
*.mov binary
*.mp4 binary
*.mp3 binary
63 changes: 37 additions & 26 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# Configs
HeadersConfig.plist

### Objective-C ###
# Xcode
#
### Xcode ###
# Build products
build/
*.ipa
*.dSYM.zip
*.dSYM

# User settings
xcuserdata/
*.xcuserstate
*.xcuserdatad

# Project files
*.pbxuser
!default.pbxuser
*.mode1v3
Expand All @@ -13,44 +22,43 @@ build/
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData

# Derived data
DerivedData/
*.hmap
*.ipa
*.xcuserstate

# CocoaPods
Pods
Pods/
Podfile.lock

# Carthage
Carthage/Build/

### Xcode ###
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.xcuserstate
# Swift Package Manager
.swiftpm/
.build/
Package.resolved

# Accio
Dependencies/
.accio/

### OSX ###
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

### macOS ###
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

Expand All @@ -63,4 +71,7 @@ Icon
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
.apdisk

# iCloud
*.icloud
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ - (void)migrateStore {
NSPersistentStoreCoordinator *coordinator = [NSPersistentStoreCoordinator MR_coordinatorWithSqliteStoreNamed:oldStoreName];
// grab the current store
NSPersistentStore *currentStore = coordinator.persistentStores.lastObject;
if (!currentStore) {
return;
}
// create a new URL
NSURL *directory = [self.fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroupIdentifier];
NSURL *newStoreURL = [directory URLByAppendingPathComponent:kCoreDataName];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ - (void)captureOutput:(__unused AVCaptureOutput *)captureOutput didOutputMetadat
AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects firstObject];
if ([metadataObj.type isEqualToString:AVMetadataObjectTypeQRCode]) {
NSString *QRCode = [metadataObj stringValue];
dispatch_sync(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate cameraService:self didScanQRCode:QRCode];
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ - (void) disconnect {
MEWConnectStatus status = self.connectionStatus;
[self _disconnect];
if (status != MEWConnectStatusDisconnected) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate MEWConnectDidDisconnected:self];
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.delegate MEWConnectDidDisconnected:strongSelf];
});
}
}
Expand Down Expand Up @@ -239,7 +241,7 @@ - (void) _createTimeoutTimer {
(1ull * NSEC_PER_SEC) / 10);
@weakify(self);
dispatch_source_set_event_handler(self.timeoutTimer, ^{
@strongify(self)
@strongify(self);
dispatch_async(dispatch_get_main_queue(), ^{
[self _timeout];
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,11 @@ class Web3Wrapper: NSObject {
guard let mnemonics = BIP39.generateMnemonicsFromEntropy(entropy: entropy) else { return nil }
guard let seed = BIP39.seedFromMmemonics(mnemonics) else { return nil }

let prefixPath = KeySettings.derivationPath(network)
guard let bip32Keystore = try? BIP32Keystore(seed: seed, password: password, prefixPath: prefixPath), bip32Keystore != nil else { return nil }
guard let keydata = try? JSONEncoder().encode(bip32Keystore!.keystoreParams) else { return nil }
guard let bip32Keystore = Web3Wrapper.createBIP32Keystore(seed: seed, password: password, network: network) else { return nil }
guard let keydata = Web3Wrapper.encodeToJSON(bip32Keystore.keystoreParams) else { return nil }
guard let encryptedKeydata = self.MEWcrypto?.encryptData(keydata, withPassword: password) else { return nil }

guard let keyAccount = bip32Keystore?.addresses?.first else { return nil }
guard let keyAccount = bip32Keystore.addresses?.first else { return nil }

self.keychainService?.saveKeydata(encryptedKeydata, forAddress:keyAccount.address, ofAccount: account, inChainID: network)

Expand All @@ -145,8 +144,7 @@ class Web3Wrapper: NSObject {
*/
@objc func validatePassword(password: String, masterToken: MasterTokenPlainObject, account: AccountPlainObject, network: BlockchainNetworkType = .ethereum) -> Bool {
guard let masterTokenAddress = masterToken.address else { return false }
guard let encryptedKeydata = self.keychainService?.obtainKeydata(ofMasterToken: masterToken, ofAccount: account, inChainID: network) else { return false }
guard let keydata = self.MEWcrypto?.decryptData(encryptedKeydata, withPassword: password) else { return false }
guard let keydata = obtainDecryptedKeydata(masterToken: masterToken, account: account, network: network, password: password) else { return false }

guard let bip32Keystore = BIP32Keystore(keydata) else { return false }
guard let address = bip32Keystore.addresses?.first else { return false }
Expand All @@ -166,10 +164,9 @@ class Web3Wrapper: NSObject {
let mnemonics: String = words.joined(separator: " ")
guard let seed = BIP39.seedFromMmemonics(mnemonics) else { return nil }

let prefixPath = KeySettings.derivationPath(network)
guard let bip32Keystore = try? BIP32Keystore(seed: seed, password: "", prefixPath: prefixPath), bip32Keystore != nil else { return nil }
guard let bip32Keystore = Web3Wrapper.createBIP32Keystore(seed: seed, password: "", network: network) else { return nil }

guard let keyAccount = bip32Keystore?.addresses?.first else { return nil }
guard let keyAccount = bip32Keystore.addresses?.first else { return nil }

return keyAccount.address
}
Expand Down Expand Up @@ -201,13 +198,12 @@ class Web3Wrapper: NSObject {
guard let hashData = Web3.Utils.hashPersonalMessage(messageData) else { return nil }
if hashData != message.messageHash { return nil }

guard let encryptedKeydata = self.keychainService?.obtainKeydata(ofMasterToken: masterToken, ofAccount: account, inChainID: network) else { return nil }
guard let keydata = self.MEWcrypto?.decryptData(encryptedKeydata, withPassword: password) else { return nil }
guard let keydata = obtainDecryptedKeydata(masterToken: masterToken, account: account, network: network, password: password) else { return nil }

guard let bip32Keystore = BIP32Keystore(keydata) else { return nil }
guard let account = bip32Keystore.addresses?.first else { return nil }
guard var privateKey = try? bip32Keystore.UNSAFE_getPrivateKeyData(password: password, account: account) else { return nil }
defer {Data.zero(&privateKey)}
guard let result = extractPrivateKey(from: keydata, password: password) else { return nil }
var privateKey = result.privateKey
let account = result.account
defer { Data.zero(&privateKey) }

guard let signedData = SECP256K1.signForRecovery(hash: hashData, privateKey: privateKey, useExtraEntropy: false).serializedSignature else { return nil }
let signedMessage = signedData.toHexString().addHexPrefix()
Expand All @@ -228,19 +224,18 @@ class Web3Wrapper: NSObject {
- Returns: Signed transaction or **nil** if something goes wrong
*/
@objc func signTransaction(_ transaction: MEWConnectTransaction, password: String, masterToken: MasterTokenPlainObject, account: AccountPlainObject, network: BlockchainNetworkType = .ethereum) -> String? {
guard let encryptedKeydata = self.keychainService?.obtainKeydata(ofMasterToken: masterToken, ofAccount: account, inChainID: network) else { return nil }
guard let keydata = self.MEWcrypto?.decryptData(encryptedKeydata, withPassword: password) else { return nil }
guard let keydata = obtainDecryptedKeydata(masterToken: masterToken, account: account, network: network, password: password) else { return nil }

guard let bip32Keystore = BIP32Keystore(keydata) else { return nil }
guard let account = bip32Keystore.addresses?.first else { return nil }
guard var privateKey = try? bip32Keystore.UNSAFE_getPrivateKeyData(password: password, account: account) else { return nil }
defer {Data.zero(&privateKey)}
guard let result = extractPrivateKey(from: keydata, password: password) else { return nil }
var privateKey = result.privateKey
let account = result.account
defer { Data.zero(&privateKey) }

guard let gasPrice = BigUInt(transaction.gasPrice.stripHexPrefix(), radix: 16) else { return nil }
guard let gasLimit = BigUInt(transaction.gas.stripHexPrefix(), radix: 16) else { return nil }
guard let value = BigUInt(transaction.value.stripHexPrefix(), radix: 16) else { return nil }
guard let gasPrice = parseHexToBigUInt(transaction.gasPrice) else { return nil }
guard let gasLimit = parseHexToBigUInt(transaction.gas) else { return nil }
guard let value = parseHexToBigUInt(transaction.value) else { return nil }
guard let data = Data.fromHex(transaction.data) else { return nil }
guard let nonce = BigUInt(transaction.nonce.stripHexPrefix(), radix: 16) else { return nil }
guard let nonce = parseHexToBigUInt(transaction.nonce) else { return nil }
let chainId = BigUInt(transaction.chainId.int64Value)

let toString: String? = transaction.to
Expand Down Expand Up @@ -275,8 +270,7 @@ class Web3Wrapper: NSObject {
@objc static func balanceRequest(forAddress address: String) -> Data? {
var request = JSONRPCRequestFabric.prepareRequest(.getBalance, parameters: [address, "latest"])
request.id = address
guard let jsonData = try? JSONEncoder().encode(request) else { return nil }
return jsonData
return Web3Wrapper.encodeToJSON(request)
}

@objc static func contractRequest(forAddress address: String, contractAddresses: [String], abi: String, method: String, options: [AnyObject] = [], transactionFields:[String]) -> Data? {
Expand All @@ -289,9 +283,11 @@ class Web3Wrapper: NSObject {
guard let fromAddress = EthereumAddress(address) else { return nil }
options.from = fromAddress

var requests:[JSONRPCrequest] = []

if contractAddresses.count > 1 {
// Optimize: pre-allocate array capacity for better performance
var requests:[JSONRPCrequest] = []
requests.reserveCapacity(contractAddresses.count)

for contractAddress in contractAddresses {
guard let ethContractAddress = EthereumAddress(contractAddress) else { return nil }
contract.address = ethContractAddress
Expand All @@ -300,8 +296,7 @@ class Web3Wrapper: NSObject {
}
requests.append(request)
}
guard let jsonData = try? JSONEncoder().encode(requests) else { return nil }
return jsonData
return Web3Wrapper.encodeToJSON(requests)
} else {
guard let contractAddress = contractAddresses.first else {
return nil
Expand All @@ -311,8 +306,7 @@ class Web3Wrapper: NSObject {
guard let request = request(from: fromAddress, contract: contract, contractAddress: ethContractAddress, method: method, parameters: methodParameters, options: options, transactionFields: transactionFields) else {
return nil
}
guard let jsonData = try? JSONEncoder().encode(request) else { return nil }
return jsonData
return Web3Wrapper.encodeToJSON(request)
}
}

Expand Down Expand Up @@ -347,6 +341,39 @@ class Web3Wrapper: NSObject {
}

//MARK: - Private

private func obtainDecryptedKeydata(masterToken: MasterTokenPlainObject, account: AccountPlainObject, network: BlockchainNetworkType, password: String) -> Data? {
guard let encryptedKeydata = self.keychainService?.obtainKeydata(ofMasterToken: masterToken, ofAccount: account, inChainID: network) else { return nil }
guard let keydata = self.MEWcrypto?.decryptData(encryptedKeydata, withPassword: password) else { return nil }
return keydata
}

private static func encodeToJSON<T: Encodable>(_ value: T) -> Data? {
return try? JSONEncoder().encode(value)
}

private func parseHexToBigUInt(_ hex: String) -> BigUInt? {
return BigUInt(hex.stripHexPrefix(), radix: 16)
}

/// Extracts the private key from BIP32 keystore data
/// - Important: Caller is responsible for securely zeroing the returned private key data using Data.zero(&privateKey)
/// - Parameters:
/// - keydata: The BIP32 keystore data
/// - password: The password to decrypt the keystore
/// - Returns: A tuple containing the private key data and ethereum account address, or nil if extraction fails
private func extractPrivateKey(from keydata: Data, password: String) -> (privateKey: Data, account: EthereumAddress)? {
guard let bip32Keystore = BIP32Keystore(keydata) else { return nil }
guard let account = bip32Keystore.addresses?.first else { return nil }
guard let privateKey = try? bip32Keystore.UNSAFE_getPrivateKeyData(password: password, account: account) else { return nil }
return (privateKey, account)
}

private static func createBIP32Keystore(seed: Data, password: String, network: BlockchainNetworkType) -> BIP32Keystore? {
let prefixPath = KeySettings.derivationPath(network)
guard let bip32Keystore = try? BIP32Keystore(seed: seed, password: password, prefixPath: prefixPath) else { return nil }
return bip32Keystore
}

private static func request(from: EthereumAddress, contract: EthereumContract, contractAddress: EthereumAddress, method: String, parameters: [AnyObject], options: Web3Options, transactionFields:[String]) -> JSONRPCrequest? {
guard var transaction = contract.method(method, parameters: parameters) else { return nil }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ extension Character {
* Returns the value of the first 8 bits of this unicode character.
* This is a correct ascii representation of this character if it is
* an ascii character.
* Optimized: access unicodeScalars directly without creating intermediate String
*/
var asciiValue: UInt32 {
get {
let s = String(self).unicodeScalars
return s[s.startIndex].value
// Every Character has at least one unicode scalar, so this is safe
return self.unicodeScalars.first!.value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ extension Array where Element == UInt32 {
let tmp = Int32(bitPattern: self.last ?? 0)
let tmpT = Int32(bitPattern: t)

//shift items
self.remove(at: 0)
self.append(UInt32(bitPattern: (tmp ^ (tmp >> 19) ^ tmpT ^ (tmpT >> 8))))
// Optimize: use direct array manipulation instead of remove(at:0) + append
// which may involve memory reallocation. This version is still O(n) but avoids
// the reallocation overhead.
let newValue = UInt32(bitPattern: (tmp ^ (tmp >> 19) ^ tmpT ^ (tmpT >> 8)))
for i in 0..<(self.count - 1) {
self[i] = self[i + 1]
}
self[self.count - 1] = newValue

let divisor = Int32.max

Expand Down
Loading