diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 98511240e..1b63c3d8f 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -258,8 +258,17 @@ final class NotifyTests: XCTestCase { private extension NotifyTests { - func sign(_ message: String) -> SigningResult { + func sign(_ message: String) -> CacaoSignature { let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId) - return .signed(try! signer.sign(message: message, privateKey: privateKey, type: .eip191)) + return try! signer.sign(message: message, privateKey: privateKey, type: .eip191) + } +} + +private extension NotifyClient { + + func register(account: Account, domain: String, isLimited: Bool = false, onSign: @escaping (String) -> CacaoSignature) async throws { + let params = try await prepareRegistration(account: account, domain: domain) + let signature = onSign(params.message) + try await register(params: params, signature: signature) } } diff --git a/Example/Shared/ImportAccount.swift b/Example/Shared/ImportAccount.swift index 00e4c04e3..a4c65fed3 100644 --- a/Example/Shared/ImportAccount.swift +++ b/Example/Shared/ImportAccount.swift @@ -91,11 +91,11 @@ enum ImportAccount: Codable { } } - func onSign(message: String) -> SigningResult { + func onSign(message: String) -> CacaoSignature { let privateKey = Data(hex: privateKey) let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create() let signature = try! signer.sign(message: message, privateKey: privateKey, type: .eip191) - return .signed(signature) + return signature } static func new() -> ImportAccount { diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 215f59493..1267374f7 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -40,7 +40,9 @@ final class ConfigurationService { Task { do { - try await Notify.instance.register(account: importAccount.account, domain: "com.walletconnect", onSign: importAccount.onSign) + let params = try await Notify.instance.prepareRegistration(account: importAccount.account, domain: "com.walletconnect") + let signature = importAccount.onSign(message: params.message) + try await Notify.instance.register(params: params, signature: signature) } catch { DispatchQueue.main.async { let logMessage = LogMessage(message: "Push Server registration failed with: \(error.localizedDescription)") diff --git a/Sources/Chat/ChatClient.swift b/Sources/Chat/ChatClient.swift index 13b66378b..80199e507 100644 --- a/Sources/Chat/ChatClient.swift +++ b/Sources/Chat/ChatClient.swift @@ -92,25 +92,34 @@ public class ChatClient { domain: String, onSign: @escaping SigningCallback ) async throws -> String { - let publicKey = try await identityClient.register( + + let params = try await identityClient.prepareRegistration( account: account, domain: domain, statement: "statement", - resources: ["https://keys.walletconnect.com"], - onSign: onSign + resources: ["https://keys.walletconnect.com"] ) - if !syncRegisterService.isRegistered(account: account) { - try await chatStorage.initializeHistory(account: account) - try await syncRegisterService.register(account: account, onSign: onSign) - } - guard !isPrivate else { - return publicKey - } + switch await onSign(params.message) { + case .signed(let signature): + let publicKey = try await identityClient.register(params: params, signature: signature) + + if !syncRegisterService.isRegistered(account: account) { + try await chatStorage.initializeHistory(account: account) + try await syncRegisterService.register(account: account, onSign: onSign) + } - try await goPublic(account: account) + guard !isPrivate else { + return publicKey + } - return publicKey + try await goPublic(account: account) + + return publicKey + + case .rejected: + fatalError("Not implemented") + } } /// Unregisters a blockchain account with previously registered identity key diff --git a/Sources/WalletConnectIdentity/IdentityClient.swift b/Sources/WalletConnectIdentity/IdentityClient.swift index a776adb09..8958b5112 100644 --- a/Sources/WalletConnectIdentity/IdentityClient.swift +++ b/Sources/WalletConnectIdentity/IdentityClient.swift @@ -22,8 +22,20 @@ public final class IdentityClient { self.logger = logger } - public func register(account: Account, domain: String, statement: String, resources: [String], onSign: SigningCallback) async throws -> String { - let pubKey = try await identityService.registerIdentity(account: account, domain: domain, statement: statement, resources: resources, onSign: onSign) + public func prepareRegistration(account: Account, + domain: String, + statement: String, + resources: [String]) async throws -> IdentityRegistrationParams + { + let registration = try await identityService.prepareRegistration(account: account, domain: domain, statement: statement, resources: resources) + logger.debug("Did prepare registration for \(account)") + return registration + } + + @discardableResult + public func register(params: IdentityRegistrationParams, signature: CacaoSignature) async throws -> String { + let account = try params.account + let pubKey = try await identityService.registerIdentity(params: params, signature: signature) logger.debug("Did register an account: \(account)") return pubKey } diff --git a/Sources/WalletConnectIdentity/IdentityError.swift b/Sources/WalletConnectIdentity/IdentityError.swift deleted file mode 100644 index d3103601a..000000000 --- a/Sources/WalletConnectIdentity/IdentityError.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -enum IdentityError: Error { - case signatureRejected -} diff --git a/Sources/WalletConnectIdentity/IdentityRegistrationParams.swift b/Sources/WalletConnectIdentity/IdentityRegistrationParams.swift new file mode 100644 index 000000000..1089c2a34 --- /dev/null +++ b/Sources/WalletConnectIdentity/IdentityRegistrationParams.swift @@ -0,0 +1,11 @@ +import Foundation + +public struct IdentityRegistrationParams { + public let message: String + public let payload: CacaoPayload + public let privateIdentityKey: SigningPrivateKey + + public var account: Account { + get throws { try Account(DIDPKHString: payload.iss) } + } +} diff --git a/Sources/WalletConnectIdentity/IdentityService.swift b/Sources/WalletConnectIdentity/IdentityService.swift index 0a974c474..96cd34215 100644 --- a/Sources/WalletConnectIdentity/IdentityService.swift +++ b/Sources/WalletConnectIdentity/IdentityService.swift @@ -25,23 +25,46 @@ actor IdentityService { self.messageFormatter = messageFormatter } - func registerIdentity(account: Account, + func prepareRegistration(account: Account, domain: String, statement: String, - resources: [String], - onSign: SigningCallback - ) async throws -> String { + resources: [String]) throws -> IdentityRegistrationParams { + + let identityKey = SigningPrivateKey() + + let payload = CacaoPayload( + iss: account.did, + domain: domain, + aud: identityKey.publicKey.did, + version: getVersion(), + nonce: getNonce(), + iat: iatProvader.iat, + nbf: nil, exp: nil, + statement: statement, + requestId: nil, + resources: resources + ) + + let message = try messageFormatter.formatMessage(from: payload) + + return IdentityRegistrationParams(message: message, payload: payload, privateIdentityKey: identityKey) + } + + // TODO: Verifications + func registerIdentity(params: IdentityRegistrationParams, signature: CacaoSignature) async throws -> String { + let account = try params.account if let identityKey = try? storage.getIdentityKey(for: account) { return identityKey.publicKey.hexRepresentation } - let identityKey = SigningPrivateKey() - let audience = identityKey.publicKey.did - let cacao = try await makeCacao(account: account, domain: domain, statement: statement, resources: resources, audience: audience, onSign: onSign) + let cacaoHeader = CacaoHeader(t: "eip4361") + let cacao = Cacao(h: cacaoHeader, p: params.payload, s: signature) + try await networkService.registerIdentity(cacao: cacao) + try storage.saveIdentityKey(params.privateIdentityKey, for: account) - return try storage.saveIdentityKey(identityKey, for: account).publicKey.hexRepresentation + return params.privateIdentityKey.publicKey.hexRepresentation } func registerInvite(account: Account) async throws -> AgreementPublicKey { @@ -89,38 +112,6 @@ actor IdentityService { private extension IdentityService { - func makeCacao(account: Account, - domain: String, - statement: String, - resources: [String], - audience: String, - onSign: SigningCallback - ) async throws -> Cacao { - - let cacaoHeader = CacaoHeader(t: "eip4361") - let cacaoPayload = CacaoPayload( - iss: account.did, - domain: domain, - aud: audience, - version: getVersion(), - nonce: getNonce(), - iat: iatProvader.iat, - nbf: nil, exp: nil, - statement: statement, - requestId: nil, - resources: resources - ) - - let result = await onSign(try messageFormatter.formatMessage(from: cacaoPayload)) - - switch result { - case .signed(let cacaoSignature): - return Cacao(h: cacaoHeader, p: cacaoPayload, s: cacaoSignature) - case .rejected: - throw IdentityError.signatureRejected - } - } - func makeIDAuth(account: Account, issuer: DIDKey, claims: Claims.Type) throws -> String { let identityKey = try storage.getIdentityKey(for: account) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index 6a3ea77e5..ba30e6956 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -22,8 +22,9 @@ public class NotifyClient { public let logger: ConsoleLogging + private let keyserverURL: URL private let pushClient: PushClient - private let identityService: NotifyIdentityService + private let identityClient: IdentityClient private let notifyStorage: NotifyStorage private let notifyAccountProvider: NotifyAccountProvider private let notifyMessageSubscriber: NotifyMessageSubscriber @@ -38,8 +39,9 @@ public class NotifyClient { private let subscriptionWatcher: SubscriptionWatcher init(logger: ConsoleLogging, + keyserverURL: URL, kms: KeyManagementServiceProtocol, - identityService: NotifyIdentityService, + identityClient: IdentityClient, pushClient: PushClient, notifyMessageSubscriber: NotifyMessageSubscriber, notifyStorage: NotifyStorage, @@ -57,8 +59,9 @@ public class NotifyClient { subscriptionWatcher: SubscriptionWatcher ) { self.logger = logger + self.keyserverURL = keyserverURL self.pushClient = pushClient - self.identityService = identityService + self.identityClient = identityClient self.notifyMessageSubscriber = notifyMessageSubscriber self.notifyStorage = notifyStorage self.deleteNotifySubscriptionRequester = deleteNotifySubscriptionRequester @@ -75,14 +78,23 @@ public class NotifyClient { self.subscriptionWatcher = subscriptionWatcher } - public func register(account: Account, domain: String, isLimited: Bool = false, onSign: @escaping SigningCallback) async throws { - try await identityService.register(account: account, domain: domain, isLimited: isLimited, onSign: onSign) - notifyAccountProvider.setAccount(account) + public func prepareRegistration(account: Account, domain: String, allApps: Bool = true) async throws -> IdentityRegistrationParams { + return try await identityClient.prepareRegistration( + account: account, + domain: domain, + statement: makeStatement(allApps: allApps), + resources: [keyserverURL.absoluteString] + ) + } + + public func register(params: IdentityRegistrationParams, signature: CacaoSignature) async throws { + try await identityClient.register(params: params, signature: signature) + notifyAccountProvider.setAccount(try params.account) try await subscriptionWatcher.start() } public func unregister(account: Account) async throws { - try await identityService.unregister(account: account) + try await identityClient.unregister(account: account) notifyWatcherAgreementKeysProvider.removeAgreement(account: account) try notifyStorage.clearDatabase(account: account) notifyAccountProvider.logout() @@ -122,7 +134,7 @@ public class NotifyClient { } public func isIdentityRegistered(account: Account) -> Bool { - return identityService.isIdentityRegistered(account: account) + return identityClient.isIdentityRegistered(account: account) } public func subscriptionsPublisher(account: Account) -> AnyPublisher<[NotifySubscription], Never> { @@ -134,6 +146,18 @@ public class NotifyClient { } } +private extension NotifyClient { + + func makeStatement(allApps: Bool) -> String { + switch allApps { + case false: + return "I further authorize this app to send me notifications. Read more at https://walletconnect.com/notifications" + case true: + return "I further authorize this app to view and manage my notifications for ALL apps. Read more at https://walletconnect.com/notifications" + } + } +} + #if targetEnvironment(simulator) extension NotifyClient { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 4d265d828..99cff45c5 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -69,12 +69,11 @@ public struct NotifyClientFactory { let notifySubscriptionsChangedRequestSubscriber = NotifySubscriptionsChangedRequestSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, kms: kms, identityClient: identityClient, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, notifySubscriptionsBuilder: notifySubscriptionsBuilder) let subscriptionWatcher = SubscriptionWatcher(notifyWatchSubscriptionsRequester: notifyWatchSubscriptionsRequester, logger: logger) - let identityService = NotifyIdentityService(keyserverURL: keyserverURL, identityClient: identityClient, logger: logger) - return NotifyClient( logger: logger, + keyserverURL: keyserverURL, kms: kms, - identityService: identityService, + identityClient: identityClient, pushClient: pushClient, notifyMessageSubscriber: notifyMessageSubscriber, notifyStorage: notifyStorage, diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyIdentityService.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyIdentityService.swift deleted file mode 100644 index 0ae95400b..000000000 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyIdentityService.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation - -final class NotifyIdentityService { - - private let keyserverURL: URL - private let identityClient: IdentityClient - private let logger: ConsoleLogging - - init(keyserverURL: URL, identityClient: IdentityClient, logger: ConsoleLogging) { - self.keyserverURL = keyserverURL - self.identityClient = identityClient - self.logger = logger - } - - public func register(account: Account, domain: String, isLimited: Bool, onSign: @escaping SigningCallback) async throws { - let statement = makeStatement(isLimited: isLimited) - _ = try await identityClient.register(account: account, - domain: domain, - statement: statement, - resources: [keyserverURL.absoluteString], - onSign: onSign) - } - - public func unregister(account: Account) async throws { - try await identityClient.unregister(account: account) - } - - func isIdentityRegistered(account: Account) -> Bool { - return identityClient.isIdentityRegistered(account: account) - } -} - -private extension NotifyIdentityService { - - func makeStatement(isLimited: Bool) -> String { - switch isLimited { - case true: - return "I further authorize this app to send me notifications. Read more at https://walletconnect.com/notifications" - case false: - return "I further authorize this app to view and manage my notifications for ALL apps. Read more at https://walletconnect.com/notifications" - } - } -}