From 36adc0214233a6dda032824ebbb2bd79e4f3a00c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Aug 2022 09:51:27 +0200 Subject: [PATCH 1/4] savepoint --- Example/IntegrationTests/Auth/AuthTests.swift | 22 ++++++++++++++++++- .../Auth/Services/Signer/MessageSigner.swift | 8 +++---- Sources/Auth/Services/Signer/Signer.swift | 2 +- Sources/Auth/Types/Cacao/CacaoSignature.swift | 2 +- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 05cb74088..0f3e1ff87 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -4,7 +4,7 @@ import WalletConnectUtils @testable import WalletConnectKMS import WalletConnectRelay import Combine -@testable import Auth +import Auth final class AuthTests: XCTestCase { var app: AuthClient! @@ -81,4 +81,24 @@ final class AuthTests: XCTestCase { .store(in: &publishers) wait(for: [responseExpectation], timeout: 2) } + + func testRespondInvalidSignature() async { + let responseExpectation = expectation(description: "invalid signature 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 invalidSignature = "43effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b" + let cacaoSignature = CacaoSignature(t: "eip191", s: invalidSignature) + 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/Sources/Auth/Services/Signer/MessageSigner.swift b/Sources/Auth/Services/Signer/MessageSigner.swift index b9b8dda11..d843138e9 100644 --- a/Sources/Auth/Services/Signer/MessageSigner.swift +++ b/Sources/Auth/Services/Signer/MessageSigner.swift @@ -8,7 +8,7 @@ protocol MessageSigning { func sign(message: String, privateKey: Data) throws -> String } -struct MessageSigner: MessageSignatureVerifying, MessageSigning { +public struct MessageSigner: MessageSignatureVerifying, MessageSigning { enum Errors: Error { case signatureValidationFailed @@ -17,17 +17,17 @@ struct MessageSigner: MessageSignatureVerifying, MessageSigning { private let signer: Signer - init(signer: Signer) { + public init(signer: Signer) { self.signer = signer } - func sign(message: String, privateKey: Data) throws -> String { + public func sign(message: String, privateKey: Data) throws -> String { guard let messageData = message.data(using: .utf8) else { throw Errors.utf8EncodingFailed } let signature = try signer.sign(message: messageData, with: privateKey) return signature.toHexString() } - func verify(signature: String, message: String, address: String) throws { + public func verify(signature: String, message: String, address: String) throws { guard let messageData = message.data(using: .utf8) else { throw Errors.utf8EncodingFailed } let signatureData = Data(hex: signature) guard try signer.isValid(signature: signatureData, message: messageData, address: address) diff --git a/Sources/Auth/Services/Signer/Signer.swift b/Sources/Auth/Services/Signer/Signer.swift index a1ba7a6e6..33760312e 100644 --- a/Sources/Auth/Services/Signer/Signer.swift +++ b/Sources/Auth/Services/Signer/Signer.swift @@ -1,7 +1,7 @@ import Foundation import Web3 -struct Signer { +public struct Signer { typealias Signature = (v: UInt, r: [UInt8], s: [UInt8]) diff --git a/Sources/Auth/Types/Cacao/CacaoSignature.swift b/Sources/Auth/Types/Cacao/CacaoSignature.swift index 2c6c4c49e..83a6eeb91 100644 --- a/Sources/Auth/Types/Cacao/CacaoSignature.swift +++ b/Sources/Auth/Types/Cacao/CacaoSignature.swift @@ -1,6 +1,6 @@ import Foundation -struct CacaoSignature: Codable, Equatable { +public struct CacaoSignature: Codable, Equatable { let t: String let s: String let m: String? = nil From ffc14fbd979277622da1ac60c0ea511bb2406316 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Aug 2022 13:35:45 +0200 Subject: [PATCH 2/4] update invalid signature test --- Example/IntegrationTests/Auth/AuthTests.swift | 9 +++++---- Sources/Auth/Services/Signer/MessageSigner.swift | 2 +- Sources/Auth/Services/Signer/Signer.swift | 2 ++ Sources/Auth/Types/Cacao/CacaoSignature.swift | 8 +++++++- Sources/Auth/Types/RespondParams.swift | 7 ++++++- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 0f3e1ff87..566fc1761 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -4,7 +4,7 @@ import WalletConnectUtils @testable import WalletConnectKMS import WalletConnectRelay import Combine -import Auth +@testable import Auth final class AuthTests: XCTestCase { var app: AuthClient! @@ -68,7 +68,7 @@ final class AuthTests: XCTestCase { 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 signature = try! MessageSigner().sign(message: message, privateKey: prvKey) let cacaoSignature = CacaoSignature(t: "eip191", s: signature) try! await wallet.respond(.success(RespondParams(id: id, signature: cacaoSignature))) } @@ -88,14 +88,15 @@ final class AuthTests: XCTestCase { try! await wallet.pair(uri: uri) wallet.authRequestPublisher.sink { [unowned self] (id, message) in Task(priority: .high) { - let invalidSignature = "43effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b" + let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b" let cacaoSignature = CacaoSignature(t: "eip191", s: invalidSignature) 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 } + guard case .failure(let error) = result else { XCTFail(); return } + // TODO - complete after reason codes are merged responseExpectation.fulfill() } .store(in: &publishers) diff --git a/Sources/Auth/Services/Signer/MessageSigner.swift b/Sources/Auth/Services/Signer/MessageSigner.swift index d843138e9..d07972a5c 100644 --- a/Sources/Auth/Services/Signer/MessageSigner.swift +++ b/Sources/Auth/Services/Signer/MessageSigner.swift @@ -17,7 +17,7 @@ public struct MessageSigner: MessageSignatureVerifying, MessageSigning { private let signer: Signer - public init(signer: Signer) { + public init(signer: Signer = Signer()) { self.signer = signer } diff --git a/Sources/Auth/Services/Signer/Signer.swift b/Sources/Auth/Services/Signer/Signer.swift index 33760312e..3d5903296 100644 --- a/Sources/Auth/Services/Signer/Signer.swift +++ b/Sources/Auth/Services/Signer/Signer.swift @@ -5,6 +5,8 @@ public struct Signer { typealias Signature = (v: UInt, r: [UInt8], s: [UInt8]) + public init() {} + func sign(message: Data, with key: Data) throws -> Data { let prefixed = prefixed(message: message) let privateKey = try EthereumPrivateKey(privateKey: key.bytes) diff --git a/Sources/Auth/Types/Cacao/CacaoSignature.swift b/Sources/Auth/Types/Cacao/CacaoSignature.swift index 83a6eeb91..b34ee1a40 100644 --- a/Sources/Auth/Types/Cacao/CacaoSignature.swift +++ b/Sources/Auth/Types/Cacao/CacaoSignature.swift @@ -3,5 +3,11 @@ import Foundation public struct CacaoSignature: Codable, Equatable { let t: String let s: String - let m: String? = nil + let m: String? + + public init(t: String, s: String, m: String? = nil) { + self.t = t + self.s = s + self.m = m + } } diff --git a/Sources/Auth/Types/RespondParams.swift b/Sources/Auth/Types/RespondParams.swift index 79926445e..f7c4a4af8 100644 --- a/Sources/Auth/Types/RespondParams.swift +++ b/Sources/Auth/Types/RespondParams.swift @@ -1,6 +1,11 @@ import Foundation -public struct RespondParams { +public struct RespondParams: Equatable { let id: RPCID let signature: CacaoSignature + + public init(id: RPCID, signature: CacaoSignature) { + self.id = id + self.signature = signature + } } From 7d157356fe2ccea475cadad502df845f935adbf3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Aug 2022 13:22:46 +0200 Subject: [PATCH 3/4] savepoint --- Example/IntegrationTests/Auth/AuthTests.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 8e343268f..fd63458af 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -81,6 +81,24 @@ final class AuthTests: XCTestCase { wait(for: [responseExpectation], timeout: 5) } + func testUserRespondError() { + let responseExpectation = expectation(description: "error 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) { + try! await wallet.respond(requestId: id, result: .failure(Never()) + } + } + .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: 5) + } + func testRespondSignatureVerificationFailed() async { let responseExpectation = expectation(description: "invalid signature response delivered") let uri = try! await app.request(RequestParams.stub()) From da0360649f796c89436ab8ab3d63db84b593dfa5 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Aug 2022 14:34:20 +0200 Subject: [PATCH 4/4] handle error response add testUserRespondError --- Example/IntegrationTests/Auth/AuthTests.swift | 11 ++++++----- Sources/Auth/AuthClient.swift | 9 +++++++-- .../Services/App/AppRespondSubscriber.swift | 11 +++++++++-- .../Services/Wallet/WalletRespondService.swift | 13 ++----------- Sources/Auth/Types/Errors/AuthError.swift | 17 +++++++++++++++++ 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index fd63458af..731d74b14 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -69,7 +69,7 @@ final class AuthTests: XCTestCase { Task(priority: .high) { let signature = try! MessageSigner().sign(message: message, privateKey: prvKey) let cacaoSignature = CacaoSignature(t: "eip191", s: signature) - try! await wallet.respond(requestId: id, result: .success(cacaoSignature)) + try! await wallet.respond(requestId: id, signature: cacaoSignature) } } .store(in: &publishers) @@ -81,18 +81,19 @@ final class AuthTests: XCTestCase { wait(for: [responseExpectation], timeout: 5) } - func testUserRespondError() { + func testUserRespondError() async { let responseExpectation = expectation(description: "error 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) { - try! await wallet.respond(requestId: id, result: .failure(Never()) + try! await wallet.reject(requestId: id) } } .store(in: &publishers) app.authResponsePublisher.sink { (id, result) in - guard case .success = result else { XCTFail(); return } + guard case .failure(let error) = result else { XCTFail(); return } + XCTAssertEqual(error, .userRejeted) responseExpectation.fulfill() } .store(in: &publishers) @@ -107,7 +108,7 @@ final class AuthTests: XCTestCase { Task(priority: .high) { let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b" let cacaoSignature = CacaoSignature(t: "eip191", s: invalidSignature) - try! await wallet.respond(requestId: id, result: .success(cacaoSignature)) + try! await wallet.respond(requestId: id, signature: cacaoSignature) } } .store(in: &publishers) diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index ad4192861..158e54a2f 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -4,6 +4,7 @@ import WalletConnectUtils import WalletConnectPairing import WalletConnectRelay + public class AuthClient { enum Errors: Error { case malformedPairingURI @@ -87,9 +88,13 @@ public class AuthClient { try await appRequestService.request(params: params, topic: topic) } - public func respond(requestId: RPCID, result: Result) async throws { + public func respond(requestId: RPCID, signature: CacaoSignature) async throws { guard let account = account else { throw Errors.unknownWalletAddress } - try await walletRespondService.respond(requestId: requestId, result: result, account: account) + try await walletRespondService.respond(requestId: requestId, signature: signature, account: account) + } + + public func reject(requestId: RPCID) async throws { + try await walletRespondService.respondError(requestId: requestId) } public func getPendingRequests() throws -> [AuthRequest] { diff --git a/Sources/Auth/Services/App/AppRespondSubscriber.swift b/Sources/Auth/Services/App/AppRespondSubscriber.swift index bc7e13c72..a73015b4c 100644 --- a/Sources/Auth/Services/App/AppRespondSubscriber.swift +++ b/Sources/Auth/Services/App/AppRespondSubscriber.swift @@ -28,16 +28,23 @@ class AppRespondSubscriber { private func subscribeForResponse() { networkingInteractor.responsePublisher.sink { [unowned self] subscriptionPayload in + let response = subscriptionPayload.response guard - let requestId = subscriptionPayload.response.id, + let requestId = response.id, let request = rpcHistory.get(recordId: requestId)?.request, let requestParams = request.params, request.method == "wc_authRequest" else { return } networkingInteractor.unsubscribe(topic: subscriptionPayload.topic) + if let errorResponse = response.error, + let error = AuthError(code: errorResponse.code) { + onResponse?(requestId, .failure(error)) + return + } + guard - let cacao = try? subscriptionPayload.response.result?.get(Cacao.self), + let cacao = try? response.result?.get(Cacao.self), let address = try? DIDPKH(iss: cacao.payload.iss).account.address, let message = try? messageFormatter.formatMessage(from: cacao.payload) else { self.onResponse?(requestId, .failure(.malformedResponseParams)); return } diff --git a/Sources/Auth/Services/Wallet/WalletRespondService.swift b/Sources/Auth/Services/Wallet/WalletRespondService.swift index 0aba57f14..07e01871c 100644 --- a/Sources/Auth/Services/Wallet/WalletRespondService.swift +++ b/Sources/Auth/Services/Wallet/WalletRespondService.swift @@ -23,16 +23,7 @@ actor WalletRespondService { self.rpcHistory = rpcHistory } - func respond(requestId: RPCID, result: Result, account: Account) async throws { - switch result { - case .success(let signature): - try await respond(requestId: requestId, signature: signature, account: account) - case .failure: - try await respondError(requestId: requestId) - } - } - - private func respond(requestId: RPCID, signature: CacaoSignature, account: Account) async throws { + func respond(requestId: RPCID, signature: CacaoSignature, account: Account) async throws { let authRequestParams = try getAuthRequestParams(requestId: requestId) let (topic, keys) = try generateAgreementKeys(requestParams: authRequestParams) @@ -44,7 +35,7 @@ actor WalletRespondService { try await networkingInteractor.respond(topic: topic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) } - private func respondError(requestId: RPCID) async throws { + func respondError(requestId: RPCID) async throws { let authRequestParams = try getAuthRequestParams(requestId: requestId) let (topic, keys) = try generateAgreementKeys(requestParams: authRequestParams) diff --git a/Sources/Auth/Types/Errors/AuthError.swift b/Sources/Auth/Types/Errors/AuthError.swift index 2b83d6182..f5b52b21a 100644 --- a/Sources/Auth/Types/Errors/AuthError.swift +++ b/Sources/Auth/Types/Errors/AuthError.swift @@ -10,6 +10,23 @@ public enum AuthError: Codable, Equatable, Error { extension AuthError: Reason { + init?(code: Int) { + switch code { + case Self.userRejeted.code: + self = .userRejeted + case Self.malformedResponseParams.code: + self = .malformedResponseParams + case Self.malformedRequestParams.code: + self = .malformedRequestParams + case Self.messageCompromised.code: + self = .messageCompromised + case Self.signatureVerificationFailed.code: + self = .signatureVerificationFailed + default: + return nil + } + } + public var code: Int { switch self { case .userRejeted: