Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Push] envelope type 1 response #767

Merged
merged 18 commits into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Example/DApp/Sign/Accounts/AccountsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<PushSubscription, PushError>) in
Push.dapp.responsePublisher.sink { (id: RPCID, result: Result<PushSubscriptionResult, PushError>) in
switch result {
case .success(let subscription):
self.pushSubscription = subscription
case .success(let subscriptionResult):
self.pushSubscription = subscriptionResult.pushSubscription
case .failure(let error):
print(error)
}
Expand Down
4 changes: 3 additions & 1 deletion Example/IntegrationTests/Pairing/PairingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")!
flypaper0 marked this conversation as resolved.
Show resolved Hide resolved
walletPushClient = WalletPushClientFactory.create(keyserverURL: keyserverURL,
logger: pushLogger,
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
groupKeychainStorage: KeychainStorageMock(),
Expand Down
39 changes: 24 additions & 15 deletions Example/IntegrationTests/Push/PushTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import WalletConnectNetworking
import WalletConnectEcho
@testable import WalletConnectPush
@testable import WalletConnectPairing
import WalletConnectIdentity
import WalletConnectSigner

final class PushTests: XCTestCase {

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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))
}
}
2 changes: 1 addition & 1 deletion Example/IntegrationTests/Stubs/Account.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")!
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions Sources/Chat/ProtocolServices/Common/MessagingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private extension InvitationHandlingService {
.sink { [unowned self] (payload: RequestSubscriptionPayload<InvitePayload.Wrapper>) 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) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Chat/ProtocolServices/Inviter/InviteService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
15 changes: 0 additions & 15 deletions Sources/WalletConnectJWT/JWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,6 @@ struct JWT<JWTClaims: JWTEncodable>: 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()
Expand Down
7 changes: 3 additions & 4 deletions Sources/WalletConnectJWT/JWTDecodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Claims>(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)
}
Expand Down
22 changes: 22 additions & 0 deletions Sources/WalletConnectJWT/JWTValidator.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
13 changes: 7 additions & 6 deletions Sources/WalletConnectKMS/Serialiser/Serializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Codable>(topic: String, encodedEnvelope: String) throws -> T {
public func deserialize<T: Codable>(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)
}
Expand All @@ -58,15 +59,15 @@ public class Serializer: Serializing {
}
}

private func handleType1Envelope<T: Codable>(_ topic: String, peerPubKey: Data, sealbox: Data) throws -> T {
private func handleType1Envelope<T: Codable>(_ 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<T: Codable>(sealbox: Data, symmetricKey: Data) throws -> T {
Expand Down
6 changes: 4 additions & 2 deletions Sources/WalletConnectKMS/Serialiser/Serializing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import Foundation

public protocol Serializing {
func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String
func deserialize<T: Codable>(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<T: Codable>(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?)
}

public extension Serializing {
func tryDeserialize<T: Codable>(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<T: Codable>(topic: String, encodedEnvelope: String) -> (T, derivedTopic: String?)? {
return try? deserialize(topic: topic, encodedEnvelope: encodedEnvelope)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/WalletConnectNetworking/NetworkInteracting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Combine

public protocol NetworkInteracting {
var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never> { 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
Expand Down
Loading