diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index f314e3f78..3bc7a26a5 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; }; 84F568C2279582D200D0A289 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; }; 84F568C42795832A00D0A289 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C32795832A00D0A289 /* EthereumTransaction.swift */; }; + 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; }; A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; }; A50F3946288005B200064555 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50F3945288005B200064555 /* Types.swift */; }; A5629AA92876A23100094373 /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AA82876A23100094373 /* ChatService.swift */; }; @@ -208,6 +209,7 @@ 84D2A66528A4F51E0088AE09 /* AuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTests.swift; sourceTree = ""; }; 84F568C1279582D200D0A289 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; }; 84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; + 84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; }; A50C036428AAD32200FE72D3 /* ClientDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientDelegate.swift; sourceTree = ""; }; A50F3945288005B200064555 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; A5629AA82876A23100094373 /* ChatService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatService.swift; sourceTree = ""; }; @@ -898,6 +900,7 @@ A5E03E1028646F8000888481 /* KeychainStorageMock.swift */, A5E03E0E28646D8A00888481 /* WebSocketFactory.swift */, A5E03DFC286465D100888481 /* Stubs.swift */, + 84FE684528ACDB4700C893FF /* RequestParams.swift */, ); path = Stubs; sourceTree = ""; @@ -1276,6 +1279,7 @@ 767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */, A5E03E03286466F400888481 /* ChatTests.swift in Sources */, 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */, + 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */, 7694A5262874296A0001257E /* RegistryTests.swift in Sources */, A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */, A5E03E0D28646AD200888481 /* RelayClientEndToEndTests.swift in Sources */, diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 1b721162f..05cb74088 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -9,11 +9,12 @@ import Combine final class AuthTests: XCTestCase { var app: AuthClient! var wallet: AuthClient! + let prvKey = Data(hex: "462c1dad6832d7d96ccf87bd6a686a4110e114aaaebd5512e552c0e3a87b480f") private var publishers = [AnyCancellable]() override func setUp() { app = makeClient(prefix: "👻 App") - let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")! + let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")! wallet = makeClient(prefix: "🤑 Wallet", account: walletAccount) let expectation = expectation(description: "Wait Clients Connected") @@ -52,6 +53,32 @@ final class AuthTests: XCTestCase { } func testRequest() async { + let requestExpectation = expectation(description: "request delivered to wallet") + let uri = try! await app.request(RequestParams.stub()) + try! await wallet.pair(uri: uri) + wallet.authRequestPublisher.sink { _ in + requestExpectation.fulfill() + }.store(in: &publishers) + wait(for: [requestExpectation], timeout: 2) + } + func testRespondSuccess() async { + let responseExpectation = expectation(description: "successful response delivered") + let uri = try! await app.request(RequestParams.stub()) + try! await wallet.pair(uri: uri) + wallet.authRequestPublisher.sink { [unowned self] (id, message) in + Task(priority: .high) { + let signature = try! MessageSigner(signer: Signer()).sign(message: message, privateKey: prvKey) + let cacaoSignature = CacaoSignature(t: "eip191", s: signature) + try! await wallet.respond(.success(RespondParams(id: id, signature: cacaoSignature))) + } + } + .store(in: &publishers) + app.authResponsePublisher.sink { (id, result) in + guard case .success = result else { XCTFail(); return } + responseExpectation.fulfill() + } + .store(in: &publishers) + wait(for: [responseExpectation], timeout: 2) } } diff --git a/Example/IntegrationTests/Stubs/RequestParams.swift b/Example/IntegrationTests/Stubs/RequestParams.swift new file mode 100644 index 000000000..89cb11ae9 --- /dev/null +++ b/Example/IntegrationTests/Stubs/RequestParams.swift @@ -0,0 +1,25 @@ + +import Foundation +@testable import Auth + +extension RequestParams { + static func stub(domain: String = "service.invalid", + chainId: String = "1", + nonce: String = "32891756", + aud: String = "https://service.invalid/login", + nbf: String? = nil, + exp: String? = nil, + statement: String? = "I accept the ServiceOrg Terms of Service: https://service.invalid/tos", + requestId: String? = nil, + resources: [String]? = ["ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/", "https://example.com/my-web2-claim.json"]) -> RequestParams { + return RequestParams(domain: domain, + chainId: chainId, + nonce: nonce, + aud: aud, + nbf: nbf, + exp: exp, + statement: statement, + requestId: requestId, + resources: resources) + } +} diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift index 6d48fa179..cf75c3ce1 100644 --- a/Sources/Auth/AuthClientFactory.swift +++ b/Sources/Auth/AuthClientFactory.swift @@ -22,7 +22,7 @@ public struct AuthClientFactory { let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) let messageFormatter = SIWEMessageFormatter() let appPairService = AppPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) - let appRequestService = AppRequestService(networkingInteractor: networkingInteractor, kms: kms, appMetadata: metadata) + let appRequestService = AppRequestService(networkingInteractor: networkingInteractor, kms: kms, appMetadata: metadata, logger: logger) let messageSigner = MessageSigner(signer: Signer()) let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingInteractor, logger: logger, rpcHistory: history, signatureVerifier: messageSigner, messageFormatter: messageFormatter) let walletPairService = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) diff --git a/Sources/Auth/Services/App/AppRequestService.swift b/Sources/Auth/Services/App/AppRequestService.swift index 216038deb..b3186e77d 100644 --- a/Sources/Auth/Services/App/AppRequestService.swift +++ b/Sources/Auth/Services/App/AppRequestService.swift @@ -7,13 +7,16 @@ actor AppRequestService { private let networkingInteractor: NetworkInteracting private let appMetadata: AppMetadata private let kms: KeyManagementService + private let logger: ConsoleLogging init(networkingInteractor: NetworkInteracting, kms: KeyManagementService, - appMetadata: AppMetadata) { + appMetadata: AppMetadata, + logger: ConsoleLogging) { self.networkingInteractor = networkingInteractor self.kms = kms self.appMetadata = appMetadata + self.logger = logger } func request(params: RequestParams, topic: String) async throws { @@ -24,6 +27,8 @@ actor AppRequestService { let payload = AuthPayload(requestParams: params, iat: issueAt) let params = AuthRequestParams(requester: requester, payloadParams: payload) let request = RPCRequest(method: "wc_authRequest", params: params) + try kms.setPublicKey(publicKey: pubKey, for: responseTopic) + logger.debug("AppRequestService: Subscribibg for response topic: \(responseTopic)") try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: AuthRequestParams.tag) try await networkingInteractor.subscribe(topic: responseTopic) } diff --git a/Sources/Auth/Services/App/AppRespondSubscriber.swift b/Sources/Auth/Services/App/AppRespondSubscriber.swift index f9005587b..cba492bf3 100644 --- a/Sources/Auth/Services/App/AppRespondSubscriber.swift +++ b/Sources/Auth/Services/App/AppRespondSubscriber.swift @@ -55,6 +55,7 @@ class AppRespondSubscriber { message: message, address: address ) + logger.debug("Received response with valid signature") onResponse?(requestId, .success(cacao)) } catch { logger.debug("Received response with invalid signature") diff --git a/Sources/Auth/Services/Common/CacaoFormatter.swift b/Sources/Auth/Services/Common/CacaoFormatter.swift index c35286b87..b8331b577 100644 --- a/Sources/Auth/Services/Common/CacaoFormatter.swift +++ b/Sources/Auth/Services/Common/CacaoFormatter.swift @@ -2,11 +2,13 @@ import Foundation import WalletConnectUtils protocol CacaoFormatting { - func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ account: Account) -> Cacao + func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ didpkh: DIDPKH) -> Cacao } class CacaoFormatter: CacaoFormatting { - func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ account: Account) -> Cacao { - fatalError("not implemented") + func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ didpkh: DIDPKH) -> Cacao { + let header = CacaoHeader(t: "eip4361") + let payload = CacaoPayload(params: request.payloadParams, didpkh: didpkh) + return Cacao(header: header, payload: payload, signature: signature) } } diff --git a/Sources/Auth/Services/Common/NetworkingInteractor.swift b/Sources/Auth/Services/Common/NetworkingInteractor.swift index d3bc9a3bb..62d152ccb 100644 --- a/Sources/Auth/Services/Common/NetworkingInteractor.swift +++ b/Sources/Auth/Services/Common/NetworkingInteractor.swift @@ -57,7 +57,13 @@ class NetworkingInteractor: NetworkInteracting { } func unsubscribe(topic: String) { - fatalError("not implemented") + relayClient.unsubscribe(topic: topic) { [unowned self] error in + if let error = error { + logger.error(error) + } else { + rpcHistory.deleteAll(forTopic: topic) + } + } } func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws { diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index cd3388428..633f2c76d 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -19,15 +19,15 @@ class WalletRequestSubscriber { self.logger = logger self.address = address self.messageFormatter = messageFormatter - subscribeForRequest() + if address != nil { + subscribeForRequest() + } } private func subscribeForRequest() { - guard let address = address else { - logger.warn("unexpected request") - return - } + guard let address = address else {return} networkingInteractor.requestPublisher.sink { [unowned self] subscriptionPayload in + logger.debug("WalletRequestSubscriber: Received request") guard let requestId = subscriptionPayload.request.id, subscriptionPayload.request.method == "wc_authRequest" else { return } diff --git a/Sources/Auth/Services/Wallet/WalletRespondService.swift b/Sources/Auth/Services/Wallet/WalletRespondService.swift index db2a598bc..6b5030df9 100644 --- a/Sources/Auth/Services/Wallet/WalletRespondService.swift +++ b/Sources/Auth/Services/Wallet/WalletRespondService.swift @@ -33,18 +33,19 @@ actor WalletRespondService { } private func respond(respondParams: RespondParams, account: Account) async throws { - guard let request = rpcHistory.get(recordId: RPCID(respondParams.id))?.request else { throw Errors.recordForIdNotFound } + guard let request = rpcHistory.get(recordId: respondParams.id)?.request else { throw Errors.recordForIdNotFound } guard let authRequestParams = try? request.params?.get(AuthRequestParams.self) else { throw Errors.malformedAuthRequestParams } - let peerPubKey = authRequestParams.requester.publicKey + let peerPubKey = try AgreementPublicKey(hex: authRequestParams.requester.publicKey) let responseTopic = peerPubKey.rawRepresentation.sha256().toHexString() let selfPubKey = try kms.createX25519KeyPair() - let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey) + let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.hexRepresentation) try kms.setAgreementSecret(agreementKeys, topic: responseTopic) - let cacao = CacaoFormatter().format(authRequestParams, respondParams.signature, account) + let didpkh = DIDPKH(account: account) + let cacao = CacaoFormatter().format(authRequestParams, respondParams.signature, didpkh) let response = RPCResponse(id: request.id!, result: cacao) - try await networkingInteractor.respond(topic: respondParams.topic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: selfPubKey.rawRepresentation)) + try await networkingInteractor.respond(topic: responseTopic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: selfPubKey.rawRepresentation)) } } diff --git a/Sources/Auth/Types/Cacao/CacaoPayload.swift b/Sources/Auth/Types/Cacao/CacaoPayload.swift index b38a3206c..1a71050c7 100644 --- a/Sources/Auth/Types/Cacao/CacaoPayload.swift +++ b/Sources/Auth/Types/Cacao/CacaoPayload.swift @@ -7,9 +7,23 @@ struct CacaoPayload: Codable, Equatable { let version: Int let nonce: String let iat: String - let nbf: String - let exp: String - let statement: String - let requestId: String - let resources: [String] + let nbf: String? + let exp: String? + let statement: String? + let requestId: String? + let resources: [String]? + + init(params: AuthPayload, didpkh: DIDPKH) { + self.iss = didpkh.iss + self.domain = params.domain + self.aud = params.aud + self.version = 1 + self.nonce = params.nonce + self.iat = params.iat + self.nbf = params.nbf + self.exp = params.exp + self.statement = params.statement + self.requestId = params.requestId + self.resources = params.resources + } } diff --git a/Sources/Auth/Types/Cacao/CacaoSignature.swift b/Sources/Auth/Types/Cacao/CacaoSignature.swift index 97c04c142..2c6c4c49e 100644 --- a/Sources/Auth/Types/Cacao/CacaoSignature.swift +++ b/Sources/Auth/Types/Cacao/CacaoSignature.swift @@ -3,5 +3,5 @@ import Foundation struct CacaoSignature: Codable, Equatable { let t: String let s: String - let m: String + let m: String? = nil } diff --git a/Sources/Auth/Types/ProtocolRPCParams/AuthRequestParams.swift b/Sources/Auth/Types/ProtocolRPCParams/AuthRequestParams.swift index c39ba3489..46feee75f 100644 --- a/Sources/Auth/Types/ProtocolRPCParams/AuthRequestParams.swift +++ b/Sources/Auth/Types/ProtocolRPCParams/AuthRequestParams.swift @@ -1,6 +1,7 @@ import Foundation import WalletConnectUtils +/// wc_authRequest RPC method request param struct AuthRequestParams: Codable, Equatable { let requester: Requester let payloadParams: AuthPayload diff --git a/Sources/Auth/Types/ProtocolRPCParams/AuthResponseParams.swift b/Sources/Auth/Types/ProtocolRPCParams/AuthResponseParams.swift index a0d64b152..2dab1477b 100644 --- a/Sources/Auth/Types/ProtocolRPCParams/AuthResponseParams.swift +++ b/Sources/Auth/Types/ProtocolRPCParams/AuthResponseParams.swift @@ -1,6 +1,7 @@ import Foundation import WalletConnectUtils +/// wc_authRequest RPC method respond param struct AuthResponseParams: Codable, Equatable { let header: CacaoHeader let payload: CacaoPayload diff --git a/Sources/Auth/Types/RespondParams.swift b/Sources/Auth/Types/RespondParams.swift index b3757a64a..79926445e 100644 --- a/Sources/Auth/Types/RespondParams.swift +++ b/Sources/Auth/Types/RespondParams.swift @@ -1,7 +1,6 @@ import Foundation public struct RespondParams { - let id: Int64 - let topic: String + let id: RPCID let signature: CacaoSignature }