@novastera-oss/nitro-metamask 0.6.3 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/NitroMetamask.podspec +12 -3
  2. package/README.md +3 -1
  3. package/android/build.gradle +14 -32
  4. package/android/cargo-ecies.gradle +60 -88
  5. package/android/src/main/aidl/io/metamask/nativesdk/IMessegeService.aidl +8 -0
  6. package/android/src/main/aidl/io/metamask/nativesdk/IMessegeServiceCallback.aidl +8 -0
  7. package/android/src/main/java/com/margelo/nitro/nitrometamask/HybridNitroMetamask.kt +101 -3
  8. package/android/src/main/java/io/metamask/androidsdk/AnyRequest.kt +8 -0
  9. package/android/src/main/java/io/metamask/androidsdk/ClientMessageServiceCallback.kt +12 -0
  10. package/android/src/main/java/io/metamask/androidsdk/ClientServiceConnection.kt +42 -0
  11. package/android/src/main/java/io/metamask/androidsdk/CommunicationClient.kt +525 -0
  12. package/android/src/main/java/io/metamask/androidsdk/CommunicationClientModule.kt +47 -0
  13. package/android/src/main/java/io/metamask/androidsdk/CommunicationClientModuleInterface.kt +11 -0
  14. package/android/src/main/java/io/metamask/androidsdk/Constants.kt +5 -0
  15. package/android/src/main/java/io/metamask/androidsdk/Crypto.kt +35 -0
  16. package/android/src/main/java/io/metamask/androidsdk/DappMetadata.kt +36 -0
  17. package/android/src/main/java/io/metamask/androidsdk/Encryption.kt +9 -0
  18. package/android/src/main/java/io/metamask/androidsdk/ErrorType.kt +41 -0
  19. package/android/src/main/java/io/metamask/androidsdk/Ethereum.kt +328 -0
  20. package/android/src/main/java/io/metamask/androidsdk/EthereumEventCallback.kt +6 -0
  21. package/android/src/main/java/io/metamask/androidsdk/EthereumMethod.kt +80 -0
  22. package/android/src/main/java/io/metamask/androidsdk/EthereumRequest.kt +7 -0
  23. package/android/src/main/java/io/metamask/androidsdk/EthereumState.kt +7 -0
  24. package/android/src/main/java/io/metamask/androidsdk/KeyExchange.kt +77 -0
  25. package/android/src/main/java/io/metamask/androidsdk/KeyExchangeMessageType.kt +20 -0
  26. package/android/src/main/java/io/metamask/androidsdk/KeyStorage.kt +122 -0
  27. package/android/src/main/java/io/metamask/androidsdk/Logger.kt +18 -0
  28. package/android/src/main/java/io/metamask/androidsdk/Message.kt +3 -0
  29. package/android/src/main/java/io/metamask/androidsdk/MessageType.kt +11 -0
  30. package/android/src/main/java/io/metamask/androidsdk/OriginatorInfo.kt +12 -0
  31. package/android/src/main/java/io/metamask/androidsdk/RequestError.kt +8 -0
  32. package/android/src/main/java/io/metamask/androidsdk/RequestInfo.kt +9 -0
  33. package/android/src/main/java/io/metamask/androidsdk/Result.kt +11 -0
  34. package/android/src/main/java/io/metamask/androidsdk/RpcRequest.kt +7 -0
  35. package/android/src/main/java/io/metamask/androidsdk/SDKInfo.kt +6 -0
  36. package/android/src/main/java/io/metamask/androidsdk/SDKOptions.kt +6 -0
  37. package/android/src/main/java/io/metamask/androidsdk/SecureStorage.kt +9 -0
  38. package/android/src/main/java/io/metamask/androidsdk/SessionConfig.kt +10 -0
  39. package/android/src/main/java/io/metamask/androidsdk/SessionManager.kt +92 -0
  40. package/android/src/main/java/io/metamask/androidsdk/SubmittedRequest.kt +8 -0
  41. package/android/src/main/java/io/metamask/androidsdk/TimeStampGenerator.kt +7 -0
  42. package/android/src/main/jniLibs/arm64-v8a/libecies.so +0 -0
  43. package/android/src/main/jniLibs/armeabi-v7a/libecies.so +0 -0
  44. package/android/src/main/jniLibs/x86/libecies.so +0 -0
  45. package/android/src/main/jniLibs/x86_64/libecies.so +0 -0
  46. package/android/src/test/java/com/margelo/nitro/nitrometamask/CancellationStateMachineTest.kt +128 -0
  47. package/android/src/test/java/com/margelo/nitro/nitrometamask/ChainIdParsingTest.kt +65 -0
  48. package/android/src/test/java/com/margelo/nitro/nitrometamask/ConfigureStateMachineTest.kt +140 -0
  49. package/android/src/test/java/com/margelo/nitro/nitrometamask/ConnectSignJsonTest.kt +76 -0
  50. package/android/src/test/java/com/margelo/nitro/nitrometamask/MetaMaskInstallationCheckTest.kt +42 -0
  51. package/android/src/test/java/com/margelo/nitro/nitrometamask/PersonalSignParamsTest.kt +75 -0
  52. package/ios/Frameworks/Ecies.xcframework/Info.plist +47 -0
  53. package/ios/Frameworks/Ecies.xcframework/ios-arm64/Headers/ecies.h +20 -0
  54. package/ios/Frameworks/Ecies.xcframework/ios-arm64/Headers/module.modulemap +4 -0
  55. package/ios/Frameworks/Ecies.xcframework/ios-arm64/libecies.a +0 -0
  56. package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/Headers/ecies.h +20 -0
  57. package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/Headers/module.modulemap +4 -0
  58. package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/libecies.a +0 -0
  59. package/ios/HybridNitroMetamask.swift +119 -54
  60. package/ios/NitroMetamaskTests/CancellationStateMachineTests.swift +150 -0
  61. package/ios/NitroMetamaskTests/ChainIdParsingTests.swift +117 -0
  62. package/ios/NitroMetamaskTests/ConfigureStateMachineTests.swift +174 -0
  63. package/ios/NitroMetamaskTests/ConnectSignJsonTests.swift +168 -0
  64. package/ios/NitroMetamaskTests/DefaultDappUrlTests.swift +80 -0
  65. package/ios/NitroMetamaskTests/PersonalSignParamsTests.swift +101 -0
  66. package/ios/metamask-ios-sdk/CommunicationLayer/CommClient.swift +43 -0
  67. package/ios/metamask-ios-sdk/CommunicationLayer/CommClientFactory.swift +17 -0
  68. package/ios/metamask-ios-sdk/CommunicationLayer/CommLayer.swift +36 -0
  69. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/Deeplink.swift +26 -0
  70. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/DeeplinkClient.swift +199 -0
  71. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/DeeplinkManager.swift +83 -0
  72. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/String.swift +48 -0
  73. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/URLOpener.swift +19 -0
  74. package/ios/metamask-ios-sdk/CommunicationLayer/SocketClient.swift +27 -0
  75. package/ios/metamask-ios-sdk/Crypto/Crypto.swift +72 -0
  76. package/ios/metamask-ios-sdk/Crypto/Encoding.swift +15 -0
  77. package/ios/metamask-ios-sdk/Crypto/KeyExchange.swift +236 -0
  78. package/ios/metamask-ios-sdk/DeviceInfo/DeviceInfo.swift +11 -0
  79. package/ios/metamask-ios-sdk/Ethereum/AppMetadata.swift +28 -0
  80. package/ios/metamask-ios-sdk/Ethereum/ErrorType.swift +62 -0
  81. package/ios/metamask-ios-sdk/Ethereum/Ethereum.swift +810 -0
  82. package/ios/metamask-ios-sdk/Ethereum/EthereumMethod.swift +111 -0
  83. package/ios/metamask-ios-sdk/Ethereum/EthereumRequest.swift +40 -0
  84. package/ios/metamask-ios-sdk/Ethereum/EthereumWrapper.swift +10 -0
  85. package/ios/metamask-ios-sdk/Ethereum/RPCRequest.swift +14 -0
  86. package/ios/metamask-ios-sdk/Ethereum/RequestError.swift +88 -0
  87. package/ios/metamask-ios-sdk/Ethereum/ResponseMethod.swift +22 -0
  88. package/ios/metamask-ios-sdk/Ethereum/SubmitRequest.swift +26 -0
  89. package/ios/metamask-ios-sdk/Ethereum/TimestampGenerator.swift +16 -0
  90. package/ios/metamask-ios-sdk/Extensions/NSRecursiveLock.swift +14 -0
  91. package/ios/metamask-ios-sdk/Extensions/Notification.swift +10 -0
  92. package/ios/metamask-ios-sdk/Logger/Logging.swift +27 -0
  93. package/ios/metamask-ios-sdk/Models/AddChainParameters.swift +35 -0
  94. package/ios/metamask-ios-sdk/Models/Event.swift +19 -0
  95. package/ios/metamask-ios-sdk/Models/Mappable.swift +40 -0
  96. package/ios/metamask-ios-sdk/Models/NativeCurrency.swift +25 -0
  97. package/ios/metamask-ios-sdk/Models/OriginatorInfo.swift +26 -0
  98. package/ios/metamask-ios-sdk/Models/RequestInfo.swift +18 -0
  99. package/ios/metamask-ios-sdk/Models/SignContract.swift +48 -0
  100. package/ios/metamask-ios-sdk/Models/Typealiases.swift +9 -0
  101. package/ios/metamask-ios-sdk/Persistence/SecureStore.swift +134 -0
  102. package/ios/metamask-ios-sdk/Persistence/SessionConfig.swift +24 -0
  103. package/ios/metamask-ios-sdk/Persistence/SessionManager.swift +56 -0
  104. package/ios/metamask-ios-sdk/SDK/Dependencies.swift +35 -0
  105. package/ios/metamask-ios-sdk/SDK/MetaMaskSDK.swift +215 -0
  106. package/ios/metamask-ios-sdk/SDK/SDKInfo.swift +37 -0
  107. package/ios/metamask-ios-sdk/SDK/SDKOptions.swift +16 -0
  108. package/lib/commonjs/index.js +50 -3
  109. package/lib/commonjs/index.js.map +1 -1
  110. package/lib/module/index.js +49 -3
  111. package/lib/module/index.js.map +1 -1
  112. package/lib/typescript/src/__tests__/parseNitroError.test.d.ts +2 -0
  113. package/lib/typescript/src/__tests__/parseNitroError.test.d.ts.map +1 -0
  114. package/lib/typescript/src/index.d.ts +43 -3
  115. package/lib/typescript/src/index.d.ts.map +1 -1
  116. package/lib/typescript/src/specs/nitro-metamask.nitro.d.ts +29 -1
  117. package/lib/typescript/src/specs/nitro-metamask.nitro.d.ts.map +1 -1
  118. package/package.json +21 -12
  119. package/react-native.config.js +5 -0
  120. package/rust/ecies-jni/Cargo.lock +50 -86
  121. package/rust/ecies-jni/Cargo.toml +1 -1
  122. package/rust/ecies-jni/src/lib.rs +164 -100
  123. package/src/__tests__/parseNitroError.test.ts +35 -0
  124. package/src/index.ts +53 -5
  125. package/src/specs/nitro-metamask.nitro.ts +29 -1
  126. package/scripts/verify-16k-page-alignment.py +0 -117
  127. package/scripts/verify-16k-page-alignment.sh +0 -5
@@ -0,0 +1,199 @@
1
+ //
2
+ // DeeplinkClient.swift
3
+ // metamask-ios-sdk
4
+ //
5
+
6
+ import UIKit
7
+ import Foundation
8
+
9
+ public class DeeplinkClient: CommClient {
10
+
11
+ private let session: SessionManager
12
+ public var channelId: String = ""
13
+ let dappScheme: String
14
+ let urlOpener: URLOpener
15
+
16
+ public var appMetadata: AppMetadata?
17
+ public var trackEvent: ((Event, [String: Any]) -> Void)?
18
+ public var handleResponse: (([String: Any]) -> Void)?
19
+ public var onClientsTerminated: (() -> Void)?
20
+
21
+ let keyExchange: KeyExchange
22
+ let deeplinkManager: DeeplinkManager
23
+ private var isConnecting = false
24
+
25
+ public var sessionDuration: TimeInterval {
26
+ get {
27
+ session.sessionDuration
28
+ } set {
29
+ session.sessionDuration = newValue
30
+ }
31
+ }
32
+
33
+ public var requestJobs: [RequestJob] = []
34
+
35
+ public init(session: SessionManager,
36
+ keyExchange: KeyExchange,
37
+ deeplinkManager: DeeplinkManager,
38
+ dappScheme: String,
39
+ urlOpener: URLOpener = DefaultURLOpener()
40
+ ) {
41
+ self.session = session
42
+ self.keyExchange = keyExchange
43
+ self.deeplinkManager = deeplinkManager
44
+ self.dappScheme = dappScheme
45
+ self.urlOpener = urlOpener
46
+ setupClient()
47
+ setupCallbacks()
48
+ }
49
+
50
+ private func setupCallbacks() {
51
+ self.deeplinkManager.onReceiveMessage = handleMessage
52
+ self.deeplinkManager.decryptMessage = keyExchange.decryptMessage
53
+ }
54
+
55
+ private func setupClient() {
56
+ let sessionInfo = session.fetchSessionConfig()
57
+ channelId = sessionInfo.0.sessionId
58
+ }
59
+
60
+ public func clearSession() {
61
+ track(event: .disconnected)
62
+ session.clear()
63
+ setupClient()
64
+ }
65
+
66
+ public func requestAuthorisation() {
67
+
68
+ }
69
+
70
+ func sendMessage(_ message: String) {
71
+ let deeplink = "metamask://\(message)"
72
+ guard
73
+ let urlString = deeplink.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
74
+ let url = URL(string: urlString)
75
+ else { return }
76
+
77
+ urlOpener.open(url)
78
+ }
79
+
80
+ public func handleUrl(_ url: URL) {
81
+ deeplinkManager.handleUrl(url)
82
+ }
83
+
84
+ func sendMessage(_ deeplink: Deeplink, options: [String: String]) {
85
+ switch deeplink {
86
+ case .connect(_, let channelId, let request):
87
+ let originatorInfo = originatorInfo().toJsonString()?.base64Encode() ?? ""
88
+ var message = "connect?scheme=\(dappScheme)&channelId=\(channelId)&comm=deeplinking&originatorInfo=\(originatorInfo)"
89
+ if let request = request {
90
+ message.append("&request=\(request)")
91
+ }
92
+ sendMessage(message)
93
+ case .mmsdk(let message, _, let channelId):
94
+ let account = options["account"] ?? ""
95
+ let chainId = options["chainId"] ?? ""
96
+ let message = "mmsdk?scheme=\(dappScheme)&message=\(message)&channelId=\(channelId ?? "")&account=\(account)@\(chainId)"
97
+ sendMessage(message)
98
+ }
99
+ }
100
+
101
+ public func connect(with request: String? = nil) {
102
+ track(event: .connectionRequest)
103
+ isConnecting = true
104
+
105
+ sendMessage(.connect(
106
+ pubkey: nil,
107
+ channelId: channelId,
108
+ request: request
109
+ ), options: [:])
110
+ }
111
+
112
+ public func track(event: Event) {
113
+ let parameters: [String: Any] = [
114
+ "id": channelId,
115
+ "commLayer": "deeplinking",
116
+ "sdkVersion": SDKInfo.version,
117
+ "url": appMetadata?.url ?? "",
118
+ "dappId": SDKInfo.bundleIdentifier ?? "N/A",
119
+ "title": appMetadata?.name ?? "",
120
+ "platform": SDKInfo.platform
121
+ ]
122
+
123
+ trackEvent?(event, parameters)
124
+ }
125
+
126
+ public func disconnect() {
127
+ track(event: .disconnected)
128
+ }
129
+
130
+ public func terminateConnection() {
131
+ track(event: .disconnected)
132
+ }
133
+
134
+ public func addRequest(_ job: @escaping RequestJob) {
135
+ requestJobs.append(job)
136
+ }
137
+
138
+ public func runQueuedJobs() {
139
+ while !requestJobs.isEmpty {
140
+ let job = requestJobs.popLast()
141
+ job?()
142
+ }
143
+ }
144
+
145
+ public func sendMessage<T>(_ message: T, encrypt: Bool, options: [String: String]) {
146
+ guard let message = message as? String else {
147
+ Logging.error("DeeplinkClient:: Expected message to be String, got \(type(of: message))")
148
+ return
149
+ }
150
+
151
+ let base64Encoded = message.base64Encode() ?? ""
152
+
153
+ let deeplink: Deeplink = .mmsdk(
154
+ message: base64Encoded,
155
+ pubkey: nil,
156
+ channelId: channelId
157
+ )
158
+ sendMessage(deeplink, options: options)
159
+ }
160
+
161
+ public func handleMessage(_ message: String) {
162
+ do {
163
+ guard let data = message.data(using: .utf8) else {
164
+ Logging.error("DeeplinkClient:: Cannot convert message to data: \(message)")
165
+ return
166
+ }
167
+
168
+ let json: [String: Any] = try JSONSerialization.jsonObject(
169
+ with: data,
170
+ options: []
171
+ )
172
+ as? [String: Any] ?? [:]
173
+
174
+ if json["type"] as? String == "terminate" {
175
+ disconnect()
176
+ Logging.log("Connection terminated")
177
+ } else if json["type"] as? String == "ready" {
178
+ Logging.log("DeeplinkClient:: Connection is ready")
179
+ runQueuedJobs()
180
+ return
181
+ }
182
+
183
+ guard let data = json["data"] as? [String: Any] else {
184
+ Logging.log("DeeplinkClient:: Ignoring response \(json)")
185
+ return
186
+ }
187
+ if
188
+ isConnecting,
189
+ data["accounts"] != nil,
190
+ data["chainId"] != nil {
191
+ isConnecting = false
192
+ track(event: .connected)
193
+ }
194
+ handleResponse?(data)
195
+ } catch {
196
+ Logging.error("DeeplinkClient:: Could not convert message to json. Message: \(message)\nError: \(error)")
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,83 @@
1
+ //
2
+ // DeeplinkManager.swift
3
+ // metamask-ios-sdk
4
+ //
5
+
6
+ import UIKit
7
+ import Foundation
8
+
9
+ public class DeeplinkManager {
10
+ public var onReceiveMessage: ((String) -> Void)?
11
+ var decryptMessage: ((String) throws -> String?)?
12
+
13
+ public init(onReceiveMessage: ( (String) -> Void)? = nil, decryptMessage: ( (String) -> String?)? = nil) {
14
+ self.onReceiveMessage = onReceiveMessage
15
+ self.decryptMessage = decryptMessage
16
+ }
17
+
18
+ public func handleUrl(_ url: URL) {
19
+ handleUrl(url.absoluteString)
20
+ }
21
+
22
+ public func handleUrl(_ url: String) {
23
+ let deeplink = getDeeplink(url)
24
+
25
+ switch deeplink {
26
+ case .mmsdk(let message, _, _):
27
+ let base64Decoded = message.base64Decode() ?? ""
28
+
29
+ onReceiveMessage?(base64Decoded)
30
+
31
+ default:
32
+ Logging.error("DeeplinkManager:: ignoring url \(url)")
33
+ }
34
+ }
35
+
36
+ public func getDeeplink(_ link: String) -> Deeplink? {
37
+
38
+ guard let url = URL(string: link) else {
39
+ Logging.error("DeeplinkManager:: Deeplink has invalid url")
40
+ return nil
41
+ }
42
+
43
+ guard url.scheme != nil else {
44
+ Logging.error("DeeplinkManager:: Deeplink is missing scheme")
45
+ return nil
46
+ }
47
+
48
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
49
+ Logging.error("DeeplinkManager:: Deeplink missing components")
50
+ return nil
51
+ }
52
+
53
+ guard let action = components.host else {
54
+ Logging.error("DeeplinkManager:: Deeplink missing action")
55
+ return nil
56
+ }
57
+
58
+ let pubkey = components.queryItems?.first(where: { $0.name == "pubkey" })?.value
59
+
60
+ if action == Deeplink.connect {
61
+ guard let channelId: String = components.queryItems?.first(where: { $0.name == "channelId" })?.value else {
62
+ Logging.error("DeeplinkManager:: Connect step missing channelId")
63
+ return nil
64
+ }
65
+
66
+ let request = components.queryItems?.first(where: { $0.name == "request" })?.value
67
+
68
+ return .connect(pubkey: pubkey, channelId: channelId, request: request)
69
+
70
+ } else if action == Deeplink.mmsdk {
71
+ guard let message = components.queryItems?.first(where: { $0.name == "message" })?.value else {
72
+ Logging.error("DeeplinkManager:: Deeplink missing message")
73
+ return nil
74
+ }
75
+
76
+ let channelId = components.queryItems?.first(where: { $0.name == "channelId" })?.value
77
+
78
+ return .mmsdk(message: message, pubkey: pubkey, channelId: channelId)
79
+ }
80
+
81
+ return nil
82
+ }
83
+ }
@@ -0,0 +1,48 @@
1
+ //
2
+ // String.swift
3
+ // metamask-ios-sdk
4
+
5
+ import Foundation
6
+
7
+ extension String {
8
+ func trimEscapingChars() -> Self {
9
+ var unescapedString = replacingOccurrences(of: #"\""#, with: "\"")
10
+ if unescapedString.hasPrefix("\"") && unescapedString.hasSuffix("\"") {
11
+ unescapedString.removeFirst()
12
+ unescapedString.removeLast()
13
+ }
14
+ return unescapedString
15
+ }
16
+ }
17
+
18
+ func json(from value: Any) -> String? {
19
+ // Recursive function to decode nested JSON strings
20
+ func decodeNestedJson(_ value: Any) -> Any {
21
+ if let arrayValue = value as? [Any] {
22
+ // If it's an array, recursively decode each element
23
+ return arrayValue.map { decodeNestedJson($0) }
24
+ } else if let dictValue = value as? [String: Any] {
25
+ // If it's a dictionary, recursively decode each value
26
+ var decodedDict = [String: Any]()
27
+ for (key, value) in dictValue {
28
+ decodedDict[key] = decodeNestedJson(value)
29
+ }
30
+ return decodedDict
31
+ } else {
32
+ // If it's neither a string, array, nor dictionary, return the value as is
33
+ return value
34
+ }
35
+ }
36
+
37
+ // Decode any nested JSON strings recursively in the input dictionary
38
+ let decodedJsonObject = decodeNestedJson(value)
39
+
40
+ // Step 3: Convert the cleaned dictionary back to a JSON string
41
+ guard let cleanedJsonData = try? JSONSerialization.data(withJSONObject: decodedJsonObject, options: []),
42
+ let cleanedJsonString = String(data: cleanedJsonData, encoding: .utf8) else {
43
+ Logging.error("Failed to serialize cleaned JSON dictionary")
44
+ return nil
45
+ }
46
+
47
+ return cleanedJsonString
48
+ }
@@ -0,0 +1,19 @@
1
+ //
2
+ // URLOpener.swift
3
+ //
4
+
5
+ import UIKit
6
+
7
+ public protocol URLOpener {
8
+ func open(_ url: URL)
9
+ }
10
+
11
+ public class DefaultURLOpener: URLOpener {
12
+ public init() {}
13
+
14
+ public func open(_ url: URL) {
15
+ DispatchQueue.main.async {
16
+ UIApplication.shared.open(url)
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,27 @@
1
+ //
2
+ // SocketClient.swift
3
+ // metamask-ios-sdk
4
+ //
5
+ // Stub — socket transport is not used. Exists only so `is SocketClient` type checks compile.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ /// Stub class. Socket transport is not supported; only deeplink transport is used.
11
+ public final class SocketClient: CommClient {
12
+ public var channelId: String = ""
13
+ public var handleResponse: (([String: Any]) -> Void)?
14
+ public var trackEvent: ((Event, [String: Any]) -> Void)?
15
+ public var onClientsTerminated: (() -> Void)?
16
+ public var appMetadata: AppMetadata?
17
+ public var sessionDuration: TimeInterval = 0
18
+ public var networkUrl: String = ""
19
+ public var useDeeplinks: Bool = false
20
+
21
+ public func connect(with request: String?) { fatalError("SocketClient not supported") }
22
+ public func disconnect() { fatalError("SocketClient not supported") }
23
+ public func clearSession() { fatalError("SocketClient not supported") }
24
+ public func requestAuthorisation() { fatalError("SocketClient not supported") }
25
+ public func addRequest(_ job: @escaping RequestJob) { fatalError("SocketClient not supported") }
26
+ public func sendMessage<T: Codable>(_ message: T, encrypt: Bool, options: [String: String]) { fatalError("SocketClient not supported") }
27
+ }
@@ -0,0 +1,72 @@
1
+ import Ecies
2
+ import Foundation
3
+
4
+ public protocol Crypto {
5
+ /// Generates keypair and returns the asymmetric private key
6
+ /// - Returns: Asymmetric private key
7
+ static func generatePrivateKey() -> String
8
+
9
+ /// Computes public key from given private key
10
+ /// - Parameter privateKey: Sender's private key
11
+ /// - Returns: Public key
12
+ static func publicKey(from privateKey: String) throws -> String
13
+
14
+ /// Encrypts plain text using provided public key
15
+ /// - Parameters:
16
+ /// - message: Plain text to encrypt
17
+ /// - publicKey: Sender public key
18
+ /// - Returns: Encrypted text
19
+ static func encrypt(_ message: String, publicKey: String) throws -> String
20
+
21
+ /// Decrypts base64 encoded cipher text to plain text using provided private key
22
+ /// - Parameters:
23
+ /// - message: base64 encoded cipher text to decrypt
24
+ /// - privateKey: Receiever's private key
25
+ /// - Returns: Decryted plain text
26
+ static func decrypt(_ message: String, privateKey: String) throws -> String
27
+ }
28
+
29
+ public enum CryptoError: Error {
30
+ case encryptionFailure
31
+ case decryptionFailure
32
+ case publicKeyGenerationFailure
33
+ }
34
+
35
+ /// Encryption module using ECIES encryption standard
36
+ public enum Ecies: Crypto {
37
+ public static func generatePrivateKey() -> String {
38
+ String(cString: ecies_generate_secret_key())
39
+ }
40
+
41
+ public static func publicKey(from privateKey: String) throws -> String {
42
+ let privateKey: NSString = privateKey as NSString
43
+ let privateKeyBytes = UnsafeMutablePointer(mutating: privateKey.utf8String)
44
+ guard let pubKeyCString = ecies_public_key_from(privateKeyBytes) else {
45
+ throw CryptoError.publicKeyGenerationFailure
46
+ }
47
+ return String(cString: pubKeyCString)
48
+ }
49
+
50
+ public static func encrypt(_ message: String, publicKey: String) throws -> String {
51
+ let message: NSString = message as NSString
52
+ let publicKey: NSString = publicKey as NSString
53
+ let messageBytes = UnsafeMutablePointer(mutating: message.utf8String)
54
+ let publicKeyBytes = UnsafeMutablePointer(mutating: publicKey.utf8String)
55
+
56
+ guard let encryptedText = ecies_encrypt(publicKeyBytes, messageBytes) else {
57
+ throw CryptoError.encryptionFailure
58
+ }
59
+ return String(cString: encryptedText)
60
+ }
61
+
62
+ public static func decrypt(_ message: String, privateKey: String) throws -> String {
63
+ let message: NSString = message as NSString
64
+ let privateKey: NSString = privateKey as NSString
65
+ let messageBytes = UnsafeMutablePointer(mutating: message.utf8String)
66
+ let privateKeyBytes = UnsafeMutablePointer(mutating: privateKey.utf8String)
67
+ guard let decryptedText = ecies_decrypt(privateKeyBytes, messageBytes) else {
68
+ throw CryptoError.decryptionFailure
69
+ }
70
+ return String(cString: decryptedText)
71
+ }
72
+ }
@@ -0,0 +1,15 @@
1
+ import Foundation
2
+
3
+ public extension String {
4
+ // Encode a string to base64
5
+ func base64Encode() -> String? {
6
+ guard let data = data(using: .utf8) else { return nil }
7
+ return data.base64EncodedString()
8
+ }
9
+
10
+ // Decode a base64 string to original string
11
+ func base64Decode() -> String? {
12
+ guard let data = Data(base64Encoded: self) else { return nil }
13
+ return String(data: data, encoding: .utf8)
14
+ }
15
+ }
@@ -0,0 +1,236 @@
1
+ //
2
+ // KeyExchange.swift
3
+ //
4
+
5
+ import Foundation
6
+
7
+ public enum KeyExchangeType: String, Mappable {
8
+ case start = "key_handshake_start"
9
+ case ack = "key_handshake_ACK"
10
+ case syn = "key_handshake_SYN"
11
+ case synack = "key_handshake_SYNACK"
12
+
13
+ public init(from decoder: Decoder) throws {
14
+ let container = try decoder.singleValueContainer()
15
+ let status = try? container.decode(String.self)
16
+ switch status {
17
+ case "key_handshake_start": self = .start
18
+ case "key_handshake_ACK": self = .ack
19
+ case "key_handshake_SYN": self = .syn
20
+ case "key_handshake_SYNACK": self = .synack
21
+ default:
22
+ self = .ack
23
+ }
24
+ }
25
+ }
26
+
27
+ public enum KeyExchangeError: Error {
28
+ case keysNotExchanged
29
+ case encodingError
30
+ }
31
+
32
+ public struct KeyExchangeMessage: CodableData, Mappable {
33
+ public let type: KeyExchangeType
34
+ public let pubkey: String?
35
+ public var v: Int?
36
+ public var clientType: String? = "dapp"
37
+
38
+ public init(type: KeyExchangeType, pubkey: String?, v: Int? = 2, clientType: String? = "dapp") {
39
+ self.type = type
40
+ self.pubkey = pubkey
41
+ self.clientType = clientType
42
+ self.v = v
43
+ }
44
+
45
+ // Custom initializer for decoding
46
+ public init(from decoder: Decoder) throws {
47
+ let container = try decoder.container(keyedBy: CodingKeys.self)
48
+ type = try container.decode(KeyExchangeType.self, forKey: .type)
49
+ pubkey = try container.decodeIfPresent(String.self, forKey: .pubkey)
50
+ v = try container.decodeIfPresent(Int.self, forKey: .v) ?? 2
51
+ clientType = try container.decodeIfPresent(String.self, forKey: .clientType) ?? "dapp"
52
+ }
53
+
54
+ // Custom method for encoding
55
+ public func encode(to encoder: Encoder) throws {
56
+ var container = encoder.container(keyedBy: CodingKeys.self)
57
+ try container.encode(type, forKey: .type)
58
+ try container.encode(pubkey, forKey: .pubkey)
59
+ try container.encode(v, forKey: .v)
60
+ try container.encode(clientType, forKey: .clientType)
61
+ }
62
+
63
+ private enum CodingKeys: String, CodingKey {
64
+ case type
65
+ case pubkey
66
+ case v
67
+ case clientType
68
+ }
69
+ }
70
+ /*
71
+ A module for handling key exchange between client and server
72
+ The key exchange sequence is defined as:
73
+ syn -> synack -> ack
74
+ */
75
+
76
+ public class KeyExchange {
77
+ private var privateKey: String
78
+ public var pubkey: String
79
+ public private(set) var theirPublicKey: String?
80
+
81
+ private let storage: SecureStore
82
+ private let encyption: Crypto.Type
83
+ var keysExchanged: Bool = false
84
+ var isKeysExchangedViaV2Protocol: Bool = false
85
+ private let privateKeyStorageKey = "MM_SDK_PRIV_KEY"
86
+ private let theirPubliKeyStorageKey = "MM_SDK_THEIR_PUB_KEY"
87
+
88
+ public init(encryption: Crypto.Type = Ecies.self, storage: SecureStore) {
89
+ self.storage = storage
90
+ self.encyption = encryption
91
+
92
+ if let storedPrivateKey = storage.string(for: privateKeyStorageKey) {
93
+ Logging.log("KeyExchange:: using stored private key")
94
+ self.privateKey = storedPrivateKey
95
+
96
+ if let theirPubKey = storage.string(for: theirPubliKeyStorageKey) {
97
+ self.theirPublicKey = theirPubKey
98
+
99
+ // wallet already has keys
100
+ keysExchanged = true
101
+ isKeysExchangedViaV2Protocol = true
102
+ }
103
+ } else {
104
+ Logging.log("KeyExchange:: generating new private key")
105
+ privateKey = encyption.generatePrivateKey()
106
+ }
107
+
108
+ do {
109
+ pubkey = try encyption.publicKey(from: privateKey)
110
+ } catch {
111
+ pubkey = ""
112
+ }
113
+ }
114
+
115
+ public func reset() {
116
+ keysExchanged = false
117
+ theirPublicKey = nil
118
+ privateKey = ""
119
+ isKeysExchangedViaV2Protocol = false
120
+
121
+ storage.deleteData(for: privateKeyStorageKey)
122
+ storage.deleteData(for: theirPubliKeyStorageKey)
123
+ privateKey = encyption.generatePrivateKey()
124
+
125
+ do {
126
+ pubkey = try encyption.publicKey(from: privateKey)
127
+ } catch {
128
+ pubkey = ""
129
+ }
130
+ }
131
+
132
+ public func nextMessage(_ message: KeyExchangeMessage) -> KeyExchangeMessage? {
133
+ if message.type == .start {
134
+ keysExchanged = false
135
+ }
136
+
137
+ if
138
+ let publicKey = message.pubkey,
139
+ !publicKey.isEmpty {
140
+ // inline: store their public key
141
+ theirPublicKey = publicKey
142
+ keysExchanged = true
143
+ storage.save(string: publicKey, key: theirPubliKeyStorageKey)
144
+ storage.save(string: privateKey, key: privateKeyStorageKey)
145
+
146
+ if message.v == 2 {
147
+ self.storage.save(string: privateKey, key: privateKeyStorageKey)
148
+ self.storage.save(string: publicKey, key: theirPubliKeyStorageKey)
149
+ isKeysExchangedViaV2Protocol = true
150
+ }
151
+ }
152
+
153
+ guard let nextStep = nextStep(message.type) else {
154
+ return nil
155
+ }
156
+
157
+ return KeyExchangeMessage(
158
+ type: nextStep,
159
+ pubkey: pubkey
160
+ )
161
+ }
162
+
163
+ public func nextStep(_ step: KeyExchangeType) -> KeyExchangeType? {
164
+ switch step {
165
+ case .start: return .syn
166
+ case .syn: return .synack
167
+ case .synack: return .ack
168
+ case .ack: return nil
169
+ }
170
+ }
171
+
172
+ public func message(type: KeyExchangeType) -> KeyExchangeMessage {
173
+ KeyExchangeMessage(
174
+ type: type,
175
+ pubkey: pubkey
176
+ )
177
+ }
178
+
179
+ public static func isHandshakeRestartMessage(_ message: [String: Any]) -> Bool {
180
+ guard
181
+ let message = message["message"] as? [String: Any],
182
+ let type = message["type"] as? String,
183
+ let exchangeType = KeyExchangeType(rawValue: type),
184
+ exchangeType == .start
185
+ else { return false }
186
+ return true
187
+ }
188
+
189
+ public func encryptMessage<T: Codable>(_ message: T) throws -> String {
190
+ guard let theirPublicKey = theirPublicKey else {
191
+ throw KeyExchangeError.keysNotExchanged
192
+ }
193
+
194
+ guard let encodedData = try? JSONEncoder().encode(message) else {
195
+ throw KeyExchangeError.encodingError
196
+ }
197
+
198
+ guard let jsonString = String(
199
+ data: encodedData,
200
+ encoding: .utf8
201
+ ) else {
202
+ throw KeyExchangeError.encodingError
203
+ }
204
+
205
+ return try encyption.encrypt(
206
+ jsonString,
207
+ publicKey: theirPublicKey
208
+ )
209
+ }
210
+
211
+ public func encrypt(_ message: String) throws -> String {
212
+ guard let theirPublicKey = theirPublicKey else {
213
+ throw KeyExchangeError.keysNotExchanged
214
+ }
215
+
216
+ return try encyption.encrypt(
217
+ message,
218
+ publicKey: theirPublicKey
219
+ )
220
+ }
221
+
222
+ public func decryptMessage(_ message: String) throws -> String {
223
+ guard theirPublicKey != nil else {
224
+ throw KeyExchangeError.keysNotExchanged
225
+ }
226
+
227
+ return try encyption.decrypt(
228
+ message,
229
+ privateKey: privateKey
230
+ ).trimEscapingChars()
231
+ }
232
+ }
233
+
234
+ extension KeyExchange {
235
+ static let live = Dependencies.shared.keyExchange
236
+ }