diff --git a/Example/DApp/Sign/Accounts/AccountsViewController.swift b/Example/DApp/Sign/Accounts/AccountsViewController.swift index d08e5287c..f51b87b0c 100644 --- a/Example/DApp/Sign/Accounts/AccountsViewController.swift +++ b/Example/DApp/Sign/Accounts/AccountsViewController.swift @@ -66,10 +66,10 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT let account = session.namespaces.values.first!.accounts.first! Task(priority: .high){ try! await Push.dapp.request(account: account, topic: session.pairingTopic)} - Push.dapp.responsePublisher.sink { (id: RPCID, result: Result) in + Push.dapp.responsePublisher.sink { (id: RPCID, result: Result) in switch result { - case .success(let subscription): - self.pushSubscription = subscription + case .success(let subscriptionResult): + self.pushSubscription = subscriptionResult.pushSubscription case .failure(let error): print(error) } diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 83e44ca16..dc0dbbfdb 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -70,7 +70,9 @@ final class PairingTests: XCTestCase { let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) walletPairingClient = pairingClient let echoClient = EchoClientFactory.create(projectId: "", clientId: "", echoHost: "echo.walletconnect.com", environment: .sandbox) - walletPushClient = WalletPushClientFactory.create(logger: pushLogger, + let keyserverURL = URL(string: "https://keys.walletconnect.com")! + walletPushClient = WalletPushClientFactory.create(keyserverURL: keyserverURL, + logger: pushLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, groupKeychainStorage: KeychainStorageMock(), diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index 4131872e2..bf7b0d59e 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -8,6 +8,8 @@ import WalletConnectNetworking import WalletConnectEcho @testable import WalletConnectPush @testable import WalletConnectPairing +import WalletConnectIdentity +import WalletConnectSigner final class PushTests: XCTestCase { @@ -70,7 +72,9 @@ final class PushTests: XCTestCase { let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) walletPairingClient = pairingClient let echoClient = EchoClientFactory.create(projectId: "", clientId: "", echoHost: "echo.walletconnect.com", environment: .sandbox) - walletPushClient = WalletPushClientFactory.create(logger: pushLogger, + let keyserverURL = URL(string: "https://keys.walletconnect.com")! + walletPushClient = WalletPushClientFactory.create(keyserverURL: keyserverURL, + logger: pushLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, groupKeychainStorage: KeychainStorageMock(), @@ -106,8 +110,7 @@ final class PushTests: XCTestCase { try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - - Task(priority: .high) { try! await walletPushClient.approve(id: id) } + Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } }.store(in: &publishers) dappPushClient.responsePublisher.sink { (_, result) in @@ -153,16 +156,16 @@ final class PushTests: XCTestCase { try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approve(id: id) } + Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } }.store(in: &publishers) dappPushClient.responsePublisher.sink { [unowned self] (_, result) in - guard case .success(let subscription) = result else { + guard case .success(let result) = result else { XCTFail() return } - pushSubscription = subscription - Task(priority: .userInitiated) { try! await dappPushClient.notify(topic: subscription.topic, message: pushMessage) } + pushSubscription = result.pushSubscription + Task(priority: .userInitiated) { try! await dappPushClient.notify(topic: result.pushSubscription.topic, message: pushMessage) } }.store(in: &publishers) walletPushClient.pushMessagePublisher.sink { [unowned self] receivedPushMessageRecord in @@ -185,16 +188,16 @@ final class PushTests: XCTestCase { var subscriptionTopic: String! walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approve(id: id) } + Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } }.store(in: &publishers) dappPushClient.responsePublisher.sink { [unowned self] (_, result) in - guard case .success(let subscription) = result else { + guard case .success(let result) = result else { XCTFail() return } - subscriptionTopic = subscription.topic - Task(priority: .userInitiated) { try! await walletPushClient.deleteSubscription(topic: subscription.topic)} + subscriptionTopic = result.pushSubscription.topic + Task(priority: .userInitiated) { try! await walletPushClient.deleteSubscription(topic: result.pushSubscription.topic)} }.store(in: &publishers) dappPushClient.deleteSubscriptionPublisher.sink { topic in @@ -212,16 +215,16 @@ final class PushTests: XCTestCase { var subscriptionTopic: String! walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approve(id: id) } + Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } }.store(in: &publishers) dappPushClient.responsePublisher.sink { [unowned self] (_, result) in - guard case .success(let subscription) = result else { + guard case .success(let result) = result else { XCTFail() return } - subscriptionTopic = subscription.topic - Task(priority: .userInitiated) { try! await dappPushClient.delete(topic: subscription.topic)} + subscriptionTopic = result.pushSubscription.topic + Task(priority: .userInitiated) { try! await dappPushClient.delete(topic: result.pushSubscription.topic)} }.store(in: &publishers) walletPushClient.deleteSubscriptionPublisher.sink { topic in @@ -230,4 +233,10 @@ final class PushTests: XCTestCase { }.store(in: &publishers) wait(for: [expectation], timeout: InputConfig.defaultTimeout) } + + private func sign(_ message: String) -> SigningResult { + let privateKey = Data(hex: "305c6cde3846927892cd32762f6120539f3ec74c9e3a16b9b798b1e85351ae2a") + let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId) + return .signed(try! signer.sign(message: message, privateKey: privateKey, type: .eip191)) + } } diff --git a/Example/IntegrationTests/Stubs/Account.swift b/Example/IntegrationTests/Stubs/Account.swift index 3363fc459..986090e41 100644 --- a/Example/IntegrationTests/Stubs/Account.swift +++ b/Example/IntegrationTests/Stubs/Account.swift @@ -3,6 +3,6 @@ import WalletConnectUtils extension Account { static func stub() -> Account { - return Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")! + return Account(chainIdentifier: "eip155:1", address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")! } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift index eb6d6f329..e6a52aa89 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift @@ -3,10 +3,17 @@ import WalletConnectPush final class PushRequestInteractor { func approve(pushRequest: PushRequest) async throws { - try await Push.wallet.approve(id: pushRequest.id) + try await Push.wallet.approve(id: pushRequest.id, onSign: onSing(_:)) } func reject(pushRequest: PushRequest) async throws { try await Push.wallet.reject(id: pushRequest.id) } + + func onSing(_ message: String) async -> SigningResult { + let privateKey = Data(hex: "e56da0e170b5e09a8bb8f1b693392c7d56c3739a9c75740fbc558a2877868540") + let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create() + let signature = try! signer.sign(message: message, privateKey: privateKey, type: .eip191) + return .signed(signature) + } } diff --git a/Package.swift b/Package.swift index a4393df00..35879ff53 100644 --- a/Package.swift +++ b/Package.swift @@ -64,7 +64,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectPush", - dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectNetworking"], + dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectNetworking", "WalletConnectIdentity"], path: "Sources/WalletConnectPush"), .target( name: "WalletConnectEcho", diff --git a/Sources/Chat/ProtocolServices/Common/MessagingService.swift b/Sources/Chat/ProtocolServices/Common/MessagingService.swift index 4beaa3e27..783aa0a24 100644 --- a/Sources/Chat/ProtocolServices/Common/MessagingService.swift +++ b/Sources/Chat/ProtocolServices/Common/MessagingService.swift @@ -64,8 +64,8 @@ private extension MessagingService { logger.debug("Received Receipt response") guard - let (message, _) = try? MessagePayload.decode(from: payload.request), - let (receipt, _) = try? ReceiptPayload.decode(from: payload.response) + let (message, _) = try? MessagePayload.decodeAndVerify(from: payload.request), + let (receipt, _) = try? ReceiptPayload.decodeAndVerify(from: payload.response) else { fatalError() /* TODO: Handle error */ } let newMessage = Message( @@ -85,7 +85,7 @@ private extension MessagingService { logger.debug("Received Message Request") - guard let (message, messageClaims) = try? MessagePayload.decode(from: payload.request) + guard let (message, messageClaims) = try? MessagePayload.decodeAndVerify(from: payload.request) else { fatalError() /* TODO: Handle error */ } Task(priority: .high) { diff --git a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift index 75fcffcb4..80d1e45f5 100644 --- a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift +++ b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift @@ -115,7 +115,7 @@ private extension InvitationHandlingService { .sink { [unowned self] (payload: RequestSubscriptionPayload) in logger.debug("Did receive an invite") - guard let (invite, claims) = try? InvitePayload.decode(from: payload.request) + guard let (invite, claims) = try? InvitePayload.decodeAndVerify(from: payload.request) else { fatalError() /* TODO: Handle error */ } Task(priority: .high) { diff --git a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift index bde61bb5f..cc0eca34f 100644 --- a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift +++ b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift @@ -88,8 +88,8 @@ private extension InviteService { logger.debug("Invite has been accepted") guard - let (invite, _) = try? InvitePayload.decode(from: payload.request), - let (accept, _) = try? AcceptPayload.decode(from: payload.response) + let (invite, _) = try? InvitePayload.decodeAndVerify(from: payload.request), + let (accept, _) = try? AcceptPayload.decodeAndVerify(from: payload.response) else { fatalError() /* TODO: Handle error */ } Task(priority: .high) { diff --git a/Sources/WalletConnectJWT/JWT.swift b/Sources/WalletConnectJWT/JWT.swift index 0e6185469..f1eab051c 100644 --- a/Sources/WalletConnectJWT/JWT.swift +++ b/Sources/WalletConnectJWT/JWT.swift @@ -28,21 +28,6 @@ struct JWT: Codable, Equatable { self.signature = try jwtSigner.sign(header: headerString, claims: claimsString) } - func isValid(publicKey: SigningPublicKey) throws -> Bool { - guard let signature else { throw JWTError.noSignature } - - let headerString = try header.encode() - let claimsString = try claims.encode() - let unsignedJWT = [headerString, claimsString].joined(separator: ".") - - guard let unsignedData = unsignedJWT.data(using: .utf8) else { - throw JWTError.invalidJWTString - } - - let signatureData = try JWTEncoder.base64urlDecodedData(string: signature) - return publicKey.isValid(signature: signatureData, for: unsignedData) - } - func encoded() throws -> String { guard let signature = signature else { throw JWTError.jwtNotSigned } let headerString = try header.encode() diff --git a/Sources/WalletConnectJWT/JWTDecodable.swift b/Sources/WalletConnectJWT/JWTDecodable.swift index 58fe76ca6..59b1871b1 100644 --- a/Sources/WalletConnectJWT/JWTDecodable.swift +++ b/Sources/WalletConnectJWT/JWTDecodable.swift @@ -23,15 +23,14 @@ public protocol JWTClaimsCodable { extension JWTClaimsCodable { - public static func decode(from wrapper: Wrapper) throws -> (Self, Claims) { + public static func decodeAndVerify(from wrapper: Wrapper) throws -> (Self, Claims) { let jwt = try JWT(string: wrapper.jwtString) let publicKey = try DIDKey(did: jwt.claims.iss) let signingPublicKey = try SigningPublicKey(rawRepresentation: publicKey.rawData) - guard try jwt.isValid(publicKey: signingPublicKey) else { - throw JWTError.signatureVerificationFailed - } + guard try JWTValidator(jwtString: wrapper.jwtString).isValid(publicKey: signingPublicKey) + else { throw JWTError.signatureVerificationFailed } return (try Self.init(claims: jwt.claims), jwt.claims) } diff --git a/Sources/WalletConnectJWT/JWTValidator.swift b/Sources/WalletConnectJWT/JWTValidator.swift new file mode 100644 index 000000000..e94a72bdf --- /dev/null +++ b/Sources/WalletConnectJWT/JWTValidator.swift @@ -0,0 +1,22 @@ +import Foundation + +struct JWTValidator { + + let jwtString: String + + func isValid(publicKey: SigningPublicKey) throws -> Bool { + var components = jwtString.components(separatedBy: ".") + + guard components.count == 3 else { throw JWTError.undefinedFormat } + + let signature = components.removeLast() + + guard let unsignedData = components + .joined(separator: ".") + .data(using: .utf8) + else { throw JWTError.invalidJWTString } + + let signatureData = try JWTEncoder.base64urlDecodedData(string: signature) + return publicKey.isValid(signature: signatureData, for: unsignedData) + } +} diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift index 96fa968ce..88abeda39 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift @@ -40,11 +40,12 @@ public class Serializer: Serializing { /// - topic: Topic that is associated with a symetric key for decrypting particular codable object /// - encodedEnvelope: Envelope to deserialize and decrypt /// - Returns: Deserialized object - public func deserialize(topic: String, encodedEnvelope: String) throws -> T { + public func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?) { let envelope = try Envelope(encodedEnvelope) switch envelope.type { case .type0: - return try handleType0Envelope(topic, envelope) + let deserialisedType: T = try handleType0Envelope(topic, envelope) + return (deserialisedType, nil) case .type1(let peerPubKey): return try handleType1Envelope(topic, peerPubKey: peerPubKey, sealbox: envelope.sealbox) } @@ -58,15 +59,15 @@ public class Serializer: Serializing { } } - private func handleType1Envelope(_ topic: String, peerPubKey: Data, sealbox: Data) throws -> T { + private func handleType1Envelope(_ topic: String, peerPubKey: Data, sealbox: Data) throws -> (T, String) { guard let selfPubKey = kms.getPublicKey(for: topic) else { throw Errors.publicKeyForTopicNotFound } let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.toHexString()) let decodedType: T = try decode(sealbox: sealbox, symmetricKey: agreementKeys.sharedKey.rawRepresentation) - let newTopic = agreementKeys.derivedTopic() - try kms.setAgreementSecret(agreementKeys, topic: newTopic) - return decodedType + let derivedTopic = agreementKeys.derivedTopic() + try kms.setAgreementSecret(agreementKeys, topic: derivedTopic) + return (decodedType, derivedTopic) } private func decode(sealbox: Data, symmetricKey: Data) throws -> T { diff --git a/Sources/WalletConnectKMS/Serialiser/Serializing.swift b/Sources/WalletConnectKMS/Serialiser/Serializing.swift index 353e846b3..81d37229f 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializing.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializing.swift @@ -2,11 +2,13 @@ import Foundation public protocol Serializing { func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String - func deserialize(topic: String, encodedEnvelope: String) throws -> T + /// - derivedTopic: topic derived from symmetric key as a result of key exchange if peers has sent envelope(type1) prefixed with it's public key + func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?) } public extension Serializing { - func tryDeserialize(topic: String, encodedEnvelope: String) -> T? { + /// - derivedTopic: topic derived from symmetric key as a result of key exchange if peers has sent envelope(type1) prefixed with it's public key + func tryDeserialize(topic: String, encodedEnvelope: String) -> (T, derivedTopic: String?)? { return try? deserialize(topic: topic, encodedEnvelope: encodedEnvelope) } diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 92a82a402..8f47c6cc9 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -3,7 +3,7 @@ import Combine public protocol NetworkInteracting { var socketConnectionStatusPublisher: AnyPublisher { get } - var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, publishedAt: Date), Never> { get } + var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never> { get } func subscribe(topic: String) async throws func unsubscribe(topic: String) func batchSubscribe(topics: [String]) async throws diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 425be69fd..7c092867b 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -9,14 +9,14 @@ public class NetworkingInteractor: NetworkInteracting { private let rpcHistory: RPCHistory private let logger: ConsoleLogging - private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, publishedAt: Date), Never>() - private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date), Never>() + private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never>() + private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>() - public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, publishedAt: Date), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never> { requestPublisherSubject.eraseToAnyPublisher() } - private var responsePublisher: AnyPublisher<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date), Never> { + private var responsePublisher: AnyPublisher<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never> { responsePublisherSubject.eraseToAnyPublisher() } @@ -71,9 +71,9 @@ public class NetworkingInteractor: NetworkInteracting { .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { (topic, rpcRequest, publishedAt) in + .compactMap { topic, rpcRequest, publishedAt, derivedTopic in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(RequestParams.self) else { return nil } - return RequestSubscriptionPayload(id: id, topic: topic, request: request, publishedAt: publishedAt) + return RequestSubscriptionPayload(id: id, topic: topic, request: request, publishedAt: publishedAt, derivedTopic: derivedTopic) } .eraseToAnyPublisher() } @@ -83,12 +83,12 @@ public class NetworkingInteractor: NetworkInteracting { .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { topic, rpcRequest, rpcResponse, publishedAt in + .compactMap { topic, rpcRequest, rpcResponse, publishedAt, derivedTopic in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self), let response = try? rpcResponse.result?.get(Response.self) else { return nil } - return ResponseSubscriptionPayload(id: id, topic: topic, request: request, response: response, publishedAt: publishedAt) + return ResponseSubscriptionPayload(id: id, topic: topic, request: request, response: response, publishedAt: publishedAt, derivedTopic: derivedTopic) } .eraseToAnyPublisher() } @@ -96,7 +96,7 @@ public class NetworkingInteractor: NetworkInteracting { public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { $0.request.method == request.method } - .compactMap { topic, rpcRequest, rpcResponse, publishedAt in + .compactMap { topic, rpcRequest, rpcResponse, publishedAt, _ in guard let id = rpcResponse.id, let request = try? rpcRequest.params?.get(Request.self), let error = rpcResponse.error else { return nil } return ResponseSubscriptionErrorPayload(id: id, topic: topic, request: request, error: error) } @@ -131,29 +131,29 @@ public class NetworkingInteractor: NetworkInteracting { } private func manageSubscription(_ topic: String, _ encodedEnvelope: String, _ publishedAt: Date) { - if let deserializedJsonRpcRequest: RPCRequest = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { - handleRequest(topic: topic, request: deserializedJsonRpcRequest, publishedAt: publishedAt) - } else if let response: RPCResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { - handleResponse(response: response, publishedAt: publishedAt) + if let (deserializedJsonRpcRequest, derivedTopic): (RPCRequest, String?) = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { + handleRequest(topic: topic, request: deserializedJsonRpcRequest, publishedAt: publishedAt, derivedTopic: derivedTopic) + } else if let (response, derivedTopic): (RPCResponse, String?) = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { + handleResponse(response: response, publishedAt: publishedAt, derivedTopic: derivedTopic) } else { logger.debug("Networking Interactor - Received unknown object type from networking relay") } } - private func handleRequest(topic: String, request: RPCRequest, publishedAt: Date) { + private func handleRequest(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?) { do { try rpcHistory.set(request, forTopic: topic, emmitedBy: .remote) - requestPublisherSubject.send((topic, request, publishedAt)) + requestPublisherSubject.send((topic, request, publishedAt, derivedTopic)) } catch { logger.debug(error) } } - private func handleResponse(response: RPCResponse, publishedAt: Date) { + private func handleResponse(response: RPCResponse, publishedAt: Date, derivedTopic: String?) { do { try rpcHistory.resolve(response) let record = rpcHistory.get(recordId: response.id!)! - responsePublisherSubject.send((record.topic, record.request, response, publishedAt)) + responsePublisherSubject.send((record.topic, record.request, response, publishedAt, derivedTopic)) } catch { logger.debug("Handle json rpc response error: \(error)") } diff --git a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift index c68192b27..1f34ceec0 100644 --- a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift +++ b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift @@ -5,11 +5,13 @@ public struct RequestSubscriptionPayload: Codable, Subscriptio public let topic: String public let request: Request public let publishedAt: Date + public let derivedTopic: String? - public init(id: RPCID, topic: String, request: Request, publishedAt: Date) { + public init(id: RPCID, topic: String, request: Request, publishedAt: Date, derivedTopic: String?) { self.id = id self.topic = topic self.request = request self.publishedAt = publishedAt + self.derivedTopic = derivedTopic } } diff --git a/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift b/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift index cd6d1c7f5..119b849b0 100644 --- a/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift +++ b/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift @@ -6,12 +6,14 @@ public struct ResponseSubscriptionPayload: public let request: Request public let response: Response public let publishedAt: Date + public let derivedTopic: String? - public init(id: RPCID, topic: String, request: Request, response: Response, publishedAt: Date) { + public init(id: RPCID, topic: String, request: Request, response: Response, publishedAt: Date, derivedTopic: String?) { self.id = id self.topic = topic self.request = request self.response = response self.publishedAt = publishedAt + self.derivedTopic = derivedTopic } } diff --git a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift index 66f1358db..6291cd1a4 100644 --- a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift +++ b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift @@ -28,7 +28,7 @@ public class PairingRequestsSubscriber { .filter { [unowned self] in !pairingProtocolMethods.contains($0.request.method)} .filter { [unowned self] in pairingStorage.hasPairing(forTopic: $0.topic)} .filter { [unowned self] in !registeredProtocolMethods.contains($0.request.method)} - .sink { [unowned self] topic, request, _ in + .sink { [unowned self] topic, request, _, _ in Task(priority: .high) { let protocolMethod = UnsupportedProtocolMethod(method: request.method) logger.debug("PairingRequestsSubscriber: responding unregistered request method") diff --git a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift b/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift index d77c906bb..91248a150 100644 --- a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift +++ b/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift @@ -17,7 +17,7 @@ public class PushDecryptionService { } public func decryptMessage(topic: String, ciphertext: String) throws -> PushMessage { - let rpcRequest: RPCRequest = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) + let (rpcRequest, _): (RPCRequest, String?) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) guard let params = rpcRequest.params else { throw Errors.malformedPushMessage } return try params.get(PushMessage.self) } diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift index f95235373..d267b68f1 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift @@ -4,9 +4,9 @@ import WalletConnectUtils public class DappPushClient { - private let responsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result), Never>() + private let responsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result), Never>() - public var responsePublisher: AnyPublisher<(id: RPCID, result: Result), Never> { + public var responsePublisher: AnyPublisher<(id: RPCID, result: Result), Never> { responsePublisherSubject.eraseToAnyPublisher() } diff --git a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift index 5e4b85ebe..603b81bbb 100644 --- a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift @@ -4,13 +4,16 @@ import WalletConnectKMS import WalletConnectNetworking class ProposalResponseSubscriber { + enum Errors: Error { + case subscriptionTopicNotDerived + } private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging private var publishers = [AnyCancellable]() private let metadata: AppMetadata private let relay: RelayProtocolOptions - var onResponse: ((_ id: RPCID, _ result: Result) -> Void)? + var onResponse: ((_ id: RPCID, _ result: Result) -> Void)? private let subscriptionsStore: CodableStore init(networkingInteractor: NetworkInteracting, @@ -32,33 +35,41 @@ class ProposalResponseSubscriber { private func subscribeForProposalResponse() { let protocolMethod = PushRequestProtocolMethod() networkingInteractor.responseSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in logger.debug("Received Push Proposal response") Task(priority: .userInitiated) { - let pushSubscription = try await handleResponse(payload: payload) - onResponse?(payload.id, .success(pushSubscription)) + do { + let (pushSubscription, jwt) = try await handleResponse(payload: payload) + let result = PushSubscriptionResult(pushSubscription: pushSubscription, subscriptionAuth: jwt) + onResponse?(payload.id, .success(result)) + } catch { + logger.error(error) + } } }.store(in: &publishers) } - private func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { - let peerPublicKeyHex = payload.response.publicKey - let selfpublicKeyHex = payload.request.publicKey - let (topic, _) = try generateAgreementKeys(peerPublicKeyHex: peerPublicKeyHex, selfpublicKeyHex: selfpublicKeyHex) + private func handleResponse(payload: ResponseSubscriptionPayload) async throws -> (PushSubscription, String) { + + let jwt = payload.response.jwtString + _ = try AcceptSubscriptionJWTPayload.decodeAndVerify(from: payload.response) + logger.debug("subscriptionAuth JWT validated") + + guard let subscriptionTopic = payload.derivedTopic else { throw Errors.subscriptionTopicNotDerived } - let pushSubscription = PushSubscription(topic: topic, account: payload.request.account, relay: relay, metadata: metadata) - subscriptionsStore.set(pushSubscription, forKey: topic) - kms.deletePrivateKey(for: selfpublicKeyHex) - try await networkingInteractor.subscribe(topic: topic) - return pushSubscription + let pushSubscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata) + logger.debug("Subscribing to Push Subscription topic: \(subscriptionTopic)") + subscriptionsStore.set(pushSubscription, forKey: subscriptionTopic) + try await networkingInteractor.subscribe(topic: subscriptionTopic) + return (pushSubscription, jwt) } - private func generateAgreementKeys(peerPublicKeyHex: String, selfpublicKeyHex: String) throws -> (topic: String, keys: AgreementKeys) { + private func generateAgreementKeys(peerPublicKeyHex: String, selfpublicKeyHex: String) throws -> String { let selfPublicKey = try AgreementPublicKey(hex: selfpublicKeyHex) let keys = try kms.performKeyAgreement(selfPublicKey: selfPublicKey, peerPublicKey: peerPublicKeyHex) let topic = keys.derivedTopic() try kms.setAgreementSecret(keys, topic: topic) - return (topic: topic, keys: keys) + return topic } private func subscribeForProposalErrors() { diff --git a/Sources/WalletConnectPush/Client/Dapp/PushProposer.swift b/Sources/WalletConnectPush/Client/Dapp/PushProposer.swift index 973e29edf..f76fdb075 100644 --- a/Sources/WalletConnectPush/Client/Dapp/PushProposer.swift +++ b/Sources/WalletConnectPush/Client/Dapp/PushProposer.swift @@ -22,8 +22,11 @@ class PushProposer { logger.debug("PushProposer: Sending Push Proposal") let protocolMethod = PushRequestProtocolMethod() let pubKey = try kms.createX25519KeyPair() + let responseTopic = pubKey.rawRepresentation.sha256().toHexString() + try kms.setPublicKey(publicKey: pubKey, for: responseTopic) let params = PushRequestParams(publicKey: pubKey.hexRepresentation, metadata: appMetadata, account: account) let request = RPCRequest(method: protocolMethod.method, params: params) try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + try await networkingInteractor.subscribe(topic: responseTopic) } } diff --git a/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift b/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift index c674d63a0..ae7b83357 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift @@ -1,4 +1,5 @@ import WalletConnectNetworking +import WalletConnectIdentity import Foundation class PushRequestResponder { @@ -6,6 +7,8 @@ class PushRequestResponder { case recordForIdNotFound case malformedRequestParams } + private let keyserverURL: URL + private let identityClient: IdentityClient private let networkingInteractor: NetworkInteracting private let kms: KeyManagementService private let rpcHistory: RPCHistory @@ -15,13 +18,17 @@ class PushRequestResponder { private let groupKeychainStorage: KeychainStorageProtocol - init(networkingInteractor: NetworkInteracting, + init(keyserverURL: URL, + networkingInteractor: NetworkInteracting, + identityClient: IdentityClient, logger: ConsoleLogging, kms: KeyManagementService, groupKeychainStorage: KeychainStorageProtocol, rpcHistory: RPCHistory, subscriptionsStore: CodableStore ) { + self.keyserverURL = keyserverURL + self.identityClient = identityClient self.networkingInteractor = networkingInteractor self.logger = logger self.kms = kms @@ -30,31 +37,39 @@ class PushRequestResponder { self.subscriptionsStore = subscriptionsStore } - func respond(requestId: RPCID) async throws { + func respond(requestId: RPCID, onSign: @escaping SigningCallback) async throws { + logger.debug("Approving Push Proposal") let requestRecord = try getRecord(requestId: requestId) let peerPublicKey = try getPeerPublicKey(for: requestRecord) - let pairingTopic = requestRecord.topic + let responseTopic = peerPublicKey.rawRepresentation.sha256().toHexString() let keys = try generateAgreementKeys(peerPublicKey: peerPublicKey) let pushTopic = keys.derivedTopic() + let requestParams = try requestRecord.request.params!.get(PushRequestParams.self) + + _ = try await identityClient.register(account: requestParams.account, onSign: onSign) + + try kms.setAgreementSecret(keys, topic: responseTopic) + + logger.debug("PushRequestResponder: responding on response topic \(responseTopic) \(pushTopic)") try kms.setAgreementSecret(keys, topic: pushTopic) try groupKeychainStorage.add(keys, forKey: pushTopic) - try await networkingInteractor.subscribe(topic: pushTopic) + logger.debug("Subscribing to push topic: \(pushTopic)") - let responseParams = PushResponseParams(publicKey: keys.publicKey.hexRepresentation) + try await networkingInteractor.subscribe(topic: pushTopic) - let response = RPCResponse(id: requestId, result: responseParams) + let response = try createJWTResponse(requestId: requestId, subscriptionAccount: requestParams.account, dappUrl: requestParams.metadata.url) - let requestParams = try requestRecord.request.params!.get(PushRequestParams.self) let pushSubscription = PushSubscription(topic: pushTopic, account: requestParams.account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: requestParams.metadata) + subscriptionsStore.set(pushSubscription, forKey: pushTopic) - try await networkingInteractor.respond(topic: pairingTopic, response: response, protocolMethod: PushRequestProtocolMethod()) + try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: PushRequestProtocolMethod(), envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) kms.deletePrivateKey(for: keys.publicKey.hexRepresentation) } @@ -67,6 +82,15 @@ class PushRequestResponder { try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: PushRequestProtocolMethod(), reason: PushError.rejected) } + private func createJWTResponse(requestId: RPCID, subscriptionAccount: Account, dappUrl: String) throws -> RPCResponse { + let jwtPayload = AcceptSubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl) + let wrapper = try identityClient.signAndCreateWrapper( + payload: jwtPayload, + account: subscriptionAccount + ) + return RPCResponse(id: requestId, result: wrapper) + } + private func getRecord(requestId: RPCID) throws -> RPCHistory.Record { guard let record = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound } @@ -74,7 +98,7 @@ class PushRequestResponder { } private func getPeerPublicKey(for record: RPCHistory.Record) throws -> AgreementPublicKey { - guard let params = try record.request.params?.get(PushResponseParams.self) + guard let params = try record.request.params?.get(PushRequestParams.self) else { throw Errors.malformedRequestParams } let peerPublicKey = try AgreementPublicKey(hex: params.publicKey) diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index 7a55a6b65..c7848a79e 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -72,8 +72,8 @@ public class WalletPushClient { setupSubscriptions() } - public func approve(id: RPCID) async throws { - try await proposeResponder.respond(requestId: id) + public func approve(id: RPCID, onSign: @escaping SigningCallback) async throws { + try await proposeResponder.respond(requestId: id, onSign: onSign) } public func reject(id: RPCID) async throws { diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index 442904fcf..ee88eb8de 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -1,17 +1,19 @@ import Foundation import WalletConnectUtils import WalletConnectEcho +import WalletConnectIdentity public struct WalletPushClientFactory { public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, echoClient: EchoClient) -> WalletPushClient { let logger = ConsoleLogger(loggingLevel: .debug) let keyValueStorage = UserDefaults.standard - + let keyserverURL = URL(string: "https://keys.walletconnect.com")! let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") let groupKeychainService = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk") return WalletPushClientFactory.create( + keyserverURL: keyserverURL, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, @@ -22,14 +24,25 @@ public struct WalletPushClientFactory { ) } - static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, groupKeychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, echoClient: EchoClient) -> WalletPushClient { + static func create( + keyserverURL: URL, + logger: ConsoleLogging, + keyValueStorage: KeyValueStorage, + keychainStorage: KeychainStorageProtocol, + groupKeychainStorage: KeychainStorageProtocol, + networkInteractor: NetworkInteracting, + pairingRegisterer: PairingRegisterer, + echoClient: EchoClient + ) -> WalletPushClient { let kms = KeyManagementService(keychain: keychainStorage) let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let subscriptionStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushSubscription) - let proposeResponder = PushRequestResponder(networkingInteractor: networkInteractor, logger: logger, kms: kms, groupKeychainStorage: groupKeychainStorage, rpcHistory: history, subscriptionsStore: subscriptionStore) + let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) + + let proposeResponder = PushRequestResponder(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, groupKeychainStorage: groupKeychainStorage, rpcHistory: history, subscriptionsStore: subscriptionStore) let pushMessagesRecordsStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushMessagesRecords) let pushMessagesDatabase = PushMessagesDatabase(store: pushMessagesRecordsStore) diff --git a/Sources/WalletConnectPush/PushImports.swift b/Sources/WalletConnectPush/PushImports.swift index 27245bda6..98325f10c 100644 --- a/Sources/WalletConnectPush/PushImports.swift +++ b/Sources/WalletConnectPush/PushImports.swift @@ -1,3 +1,5 @@ #if !CocoaPods @_exported import WalletConnectPairing +@_exported import WalletConnectSigner +@_exported import WalletConnectIdentity #endif diff --git a/Sources/WalletConnectPush/RPCRequests/AcceptSubscriptionJWTPayload.swift b/Sources/WalletConnectPush/RPCRequests/AcceptSubscriptionJWTPayload.swift new file mode 100644 index 000000000..3ca3f0bcc --- /dev/null +++ b/Sources/WalletConnectPush/RPCRequests/AcceptSubscriptionJWTPayload.swift @@ -0,0 +1,58 @@ +import Foundation + +struct AcceptSubscriptionJWTPayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// timestamp when jwt was issued + let iat: UInt64 + /// timestamp when jwt must expire + let exp: UInt64 + /// did:key of an identity key. Enables to resolve attached blockchain account. + let iss: String + /// key server for identity key verification + let ksu: String + /// dapp's url + let aud: String + /// blockchain account that push subscription has been proposed for (did:pkh) + let sub: String + } + + struct Wrapper: JWTWrapper { + let subscriptionAuth: String + + init(jwtString: String) { + self.subscriptionAuth = jwtString + } + + var jwtString: String { + return subscriptionAuth + } + } + + let keyserver: URL + let subscriptionAccount: Account + let dappUrl: String + + init(keyserver: URL, subscriptionAccount: Account, dappUrl: String) { + self.keyserver = keyserver + self.subscriptionAccount = subscriptionAccount + self.dappUrl = dappUrl + } + + init(claims: Claims) throws { + self.keyserver = try claims.ksu.asURL() + self.subscriptionAccount = try Account(DIDPKHString: claims.sub) + self.dappUrl = claims.aud + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: expiry(days: 1), + exp: defaultIatMilliseconds(), + iss: iss, + ksu: keyserver.absoluteString, + aud: dappUrl, + sub: subscriptionAccount.did + ) + } +} diff --git a/Sources/WalletConnectPush/RPCRequests/PushResponseParams.swift b/Sources/WalletConnectPush/RPCRequests/PushResponseParams.swift deleted file mode 100644 index 04fafb7e2..000000000 --- a/Sources/WalletConnectPush/RPCRequests/PushResponseParams.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public struct PushResponseParams: Codable, Equatable { - let publicKey: String -} diff --git a/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift b/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift new file mode 100644 index 000000000..3e7044917 --- /dev/null +++ b/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift @@ -0,0 +1,7 @@ + +import Foundation + +public struct PushSubscriptionResult: Equatable, Codable { + public let pushSubscription: PushSubscription + public let subscriptionAuth: String +} diff --git a/Tests/AuthTests/AppRespondSubscriberTests.swift b/Tests/AuthTests/AppRespondSubscriberTests.swift index cd2fb8f5b..3ec36ed13 100644 --- a/Tests/AuthTests/AppRespondSubscriberTests.swift +++ b/Tests/AuthTests/AppRespondSubscriberTests.swift @@ -65,7 +65,7 @@ class AppRespondSubscriberTests: XCTestCase { let cacao = Cacao(h: cacaoHeader, p: cacaoPayload, s: cacaoSignature) let response = RPCResponse(id: requestId, result: cacao) - networkingInteractor.responsePublisherSubject.send((topic, request, response, Date())) + networkingInteractor.responsePublisherSubject.send((topic, request, response, Date(), nil)) wait(for: [messageExpectation], timeout: defaultTimeout) XCTAssertTrue(pairingRegisterer.isActivateCalled) diff --git a/Tests/AuthTests/WalletRequestSubscriberTests.swift b/Tests/AuthTests/WalletRequestSubscriberTests.swift index 2f504b49d..4cb6e3c68 100644 --- a/Tests/AuthTests/WalletRequestSubscriberTests.swift +++ b/Tests/AuthTests/WalletRequestSubscriberTests.swift @@ -41,7 +41,7 @@ class WalletRequestSubscriberTests: XCTestCase { messageExpectation.fulfill() } - let payload = RequestSubscriptionPayload(id: expectedRequestId, topic: "123", request: AuthRequestParams.stub(id: expectedRequestId, iat: iat), publishedAt: Date()) + let payload = RequestSubscriptionPayload(id: expectedRequestId, topic: "123", request: AuthRequestParams.stub(id: expectedRequestId, iat: iat), publishedAt: Date(), derivedTopic: nil) pairingRegisterer.subject.send(payload) diff --git a/Tests/RelayerTests/AuthTests/JWTTests.swift b/Tests/RelayerTests/AuthTests/JWTTests.swift index e6fdbf0fc..0034c970c 100644 --- a/Tests/RelayerTests/AuthTests/JWTTests.swift +++ b/Tests/RelayerTests/AuthTests/JWTTests.swift @@ -14,6 +14,15 @@ final class JWTTests: XCTestCase { let encoded = try! jwt.encoded() XCTAssertEqual(expectedJWT, encoded) } + + func testBase64Encoding() throws { + let signature = "gf8ZZb04-6DeqhboeA-I7EucdSVuLCJmKcPSTHJqG0CBfVKn0YihgosaD9-6gXED8Itrx5EsyEi49kLmTvS8DA" + + let data = try JWTEncoder.base64urlDecodedData(string: signature) + let string = JWTEncoder.base64urlEncodedString(data: data) + + XCTAssertEqual(signature, string) + } } extension AuthPayload.Claims { diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 18da4e159..ed6edbd63 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -30,14 +30,14 @@ public class NetworkingInteractorMock: NetworkInteracting { socketConnectionStatusPublisherSubject.eraseToAnyPublisher() } - public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, publishedAt: Date), Never>() - public let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date), Never>() + public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never>() + public let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>() - public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, publishedAt: Date), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: JSONRPC.RPCRequest, publishedAt: Date, derivedTopic: String?), Never> { requestPublisherSubject.eraseToAnyPublisher() } - private var responsePublisher: AnyPublisher<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date), Never> { + private var responsePublisher: AnyPublisher<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never> { responsePublisherSubject.eraseToAnyPublisher() } @@ -47,9 +47,9 @@ public class NetworkingInteractorMock: NetworkInteracting { .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { topic, rpcRequest, publishedAt in + .compactMap { topic, rpcRequest, publishedAt, derivedTopic in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil } - return RequestSubscriptionPayload(id: id, topic: topic, request: request, publishedAt: publishedAt) + return RequestSubscriptionPayload(id: id, topic: topic, request: request, publishedAt: publishedAt, derivedTopic: derivedTopic) } .eraseToAnyPublisher() } @@ -60,12 +60,12 @@ public class NetworkingInteractorMock: NetworkInteracting { .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { topic, rpcRequest, rpcResponse, publishedAt in + .compactMap { topic, rpcRequest, rpcResponse, publishedAt, derivedTopic in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self), let response = try? rpcResponse.result?.get(Response.self) else { return nil } - return ResponseSubscriptionPayload(id: id, topic: topic, request: request, response: response, publishedAt: publishedAt) + return ResponseSubscriptionPayload(id: id, topic: topic, request: request, response: response, publishedAt: publishedAt, derivedTopic: derivedTopic) } .eraseToAnyPublisher() } @@ -74,7 +74,7 @@ public class NetworkingInteractorMock: NetworkInteracting { public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { $0.request.method == request.method } - .compactMap { (topic, rpcRequest, rpcResponse, publishedAt) in + .compactMap { (topic, rpcRequest, rpcResponse, publishedAt, _) in guard let id = rpcResponse.id, let request = try? rpcRequest.params?.get(Request.self), let error = rpcResponse.error else { return nil } return ResponseSubscriptionErrorPayload(id: id, topic: topic, request: request, error: error) } diff --git a/Tests/WalletConnectKMSTests/SerialiserTests.swift b/Tests/WalletConnectKMSTests/SerialiserTests.swift index 2c6ca0384..02822ff25 100644 --- a/Tests/WalletConnectKMSTests/SerialiserTests.swift +++ b/Tests/WalletConnectKMSTests/SerialiserTests.swift @@ -23,7 +23,7 @@ final class SerializerTests: XCTestCase { _ = try! myKms.createSymmetricKey(topic) let messageToSerialize = "todo - change for request object" let serializedMessage = try! mySerializer.serialize(topic: topic, encodable: messageToSerialize, envelopeType: .type0) - let deserializedMessage: String? = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage) + let (deserializedMessage, _): (String, String?) = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage)! XCTAssertEqual(messageToSerialize, deserializedMessage) } @@ -39,7 +39,7 @@ final class SerializerTests: XCTestCase { let serializedMessage = try! peerSerializer.serialize(topic: topic, encodable: messageToSerialize, envelopeType: .type1(pubKey: peerPubKey.rawRepresentation)) print(agreementKeys.sharedKey.hexRepresentation) // -----------Me Deserialising ------------------- - let deserializedMessage: String? = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage) + let (deserializedMessage, _): (String, String?) = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage)! XCTAssertEqual(messageToSerialize, deserializedMessage) } } diff --git a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift index f516ffad2..27a48066d 100644 --- a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift +++ b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift @@ -114,7 +114,7 @@ final class AppProposalServiceTests: XCTestCase { exp.fulfill() } - networkingInteractor.responsePublisherSubject.send((topicA, request, response, Date())) + networkingInteractor.responsePublisherSubject.send((topicA, request, response, Date(), nil)) let privateKey = try! cryptoMock.getPrivateKey(for: proposal.proposer.publicKey)! let topicB = deriveTopic(publicKey: responder.publicKey, privateKey: privateKey) _ = storageMock.getPairing(forTopic: topicA)! @@ -143,7 +143,7 @@ final class AppProposalServiceTests: XCTestCase { } let response = RPCResponse.stubError(forRequest: request) - networkingInteractor.responsePublisherSubject.send((topicA, request, response, Date())) + networkingInteractor.responsePublisherSubject.send((topicA, request, response, Date(), nil)) XCTAssert(networkingInteractor.didUnsubscribe(to: pairing.topic), "Proposer must unsubscribe if pairing is inactive.") XCTAssertFalse(storageMock.hasPairing(forTopic: pairing.topic), "Proposer must delete an inactive pairing.") @@ -171,7 +171,7 @@ final class AppProposalServiceTests: XCTestCase { storageMock.setPairing(storedPairing) let response = RPCResponse.stubError(forRequest: request) - networkingInteractor.responsePublisherSubject.send((topicA, request, response, Date())) + networkingInteractor.responsePublisherSubject.send((topicA, request, response, Date(), nil)) XCTAssertFalse(networkingInteractor.didUnsubscribe(to: pairing.topic), "Proposer must not unsubscribe if pairing is active.") XCTAssert(storageMock.hasPairing(forTopic: pairing.topic), "Proposer must not delete an active pairing.") diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index e4fd7ec11..3bcc34e01 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -60,7 +60,7 @@ final class ApproveEngineTests: XCTestCase { pairingStorageMock.setPairing(pairing) let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey) - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, publishedAt: Date())) + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, publishedAt: Date(), derivedTopic: nil)) try await engine.approveProposal(proposerPubKey: proposal.proposer.publicKey, validating: SessionNamespace.stubDictionary()) @@ -84,7 +84,7 @@ final class ApproveEngineTests: XCTestCase { sessionProposed = true } - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, publishedAt: Date())) + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, publishedAt: Date(), derivedTopic: nil)) XCTAssertNotNil(try! proposalPayloadsStore.get(key: proposal.proposer.publicKey), "Proposer must store proposal payload") XCTAssertTrue(sessionProposed) } @@ -108,7 +108,7 @@ final class ApproveEngineTests: XCTestCase { didCallBackOnSessionApproved = true } sessionTopicToProposal.set(SessionProposal.stub().publicRepresentation(pairingTopic: ""), forKey: sessionTopic) - networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle(), Date())) + networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle(), Date(), nil)) usleep(100) @@ -124,7 +124,7 @@ final class ApproveEngineTests: XCTestCase { let request = RPCRequest(method: SessionSettleProtocolMethod().method, params: SessionType.SettleParams.stub()) let response = RPCResponse(matchingRequest: request, result: RPCResult.response(AnyCodable(true))) - networkingInteractor.responsePublisherSubject.send((session.topic, request, response, Date())) + networkingInteractor.responsePublisherSubject.send((session.topic, request, response, Date(), nil)) XCTAssertTrue(sessionStorageMock.getSession(forTopic: session.topic)!.acknowledged, "Responder must acknowledged session") } @@ -139,7 +139,7 @@ final class ApproveEngineTests: XCTestCase { let request = RPCRequest(method: SessionSettleProtocolMethod().method, params: SessionType.SettleParams.stub()) let response = RPCResponse.stubError(forRequest: request) - networkingInteractor.responsePublisherSubject.send((session.topic, request, response, Date())) + networkingInteractor.responsePublisherSubject.send((session.topic, request, response, Date(), nil)) XCTAssertNil(sessionStorageMock.getSession(forTopic: session.topic), "Responder must remove session") XCTAssertTrue(networkingInteractor.didUnsubscribe(to: session.topic), "Responder must unsubscribe topic B") diff --git a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift index bc6e0a9eb..ceca38ac3 100644 --- a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift +++ b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift @@ -35,7 +35,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { didCallbackUpdatMethods = true XCTAssertEqual(topic, session.topic) } - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Date())) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Date(), nil)) XCTAssertTrue(didCallbackUpdatMethods) usleep(100) XCTAssertTrue(networkingInteractor.didRespondSuccess) @@ -51,7 +51,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { // } func testUpdateMethodPeerErrorSessionNotFound() { - networkingInteractor.requestPublisherSubject.send(("", RPCRequest.stubUpdateNamespaces(), Date())) + networkingInteractor.requestPublisherSubject.send(("", RPCRequest.stubUpdateNamespaces(), Date(), nil)) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 7001) @@ -60,7 +60,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { func testUpdateMethodPeerErrorUnauthorized() { let session = WCSession.stub(isSelfController: true) // Peer is not a controller storageMock.setSession(session) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Date())) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Date(), nil)) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 3003) @@ -74,7 +74,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Date())) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Date(), nil)) let extendedSession = storageMock.getAll().first {$0.topic == session.topic}! print(extendedSession.expiryDate) @@ -87,7 +87,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Date())) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Date(), nil)) let potentiallyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentiallyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended for peer non controller request ") @@ -98,7 +98,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { let session = WCSession.stub(isSelfController: false, expiryDate: tomorrow) storageMock.setSession(session) let tenDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: tenDaysFromNowTimestamp), Date())) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: tenDaysFromNowTimestamp), Date(), nil)) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to high") @@ -110,7 +110,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let oneDayFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: oneDayFromNowTimestamp), Date())) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: oneDayFromNowTimestamp), Date(), nil)) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to low") } diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index 74edb5418..c5a2d36e6 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -47,7 +47,7 @@ final class SessionEngineTests: XCTestCase { expiry: UInt64(Date().timeIntervalSince1970) ) - networkingInteractor.requestPublisherSubject.send(("topic", request, Date())) + networkingInteractor.requestPublisherSubject.send(("topic", request, Date(), nil)) wait(for: [expectation], timeout: 0.5) }