Skip to content

Commit

Permalink
Merge pull request #546 from WalletConnect/feature/eip1271
Browse files Browse the repository at this point in the history
[Auth] EIP1271 signature verification
  • Loading branch information
flypaper0 authored Oct 19, 2022
2 parents f5f5d8e + b3014f6 commit 3fb5c2a
Show file tree
Hide file tree
Showing 65 changed files with 493 additions and 169 deletions.
11 changes: 6 additions & 5 deletions Example/DApp/Auth/AuthViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ final class AuthViewModel: ObservableObject {
private var disposeBag = Set<AnyCancellable>()

@Published var state: SigningState = .none
@Published var uri: String?
@Published var uriString: String?

var qrImage: UIImage? {
return uri.map { QRCodeGenerator.generateQRCode(from: $0) }
return uriString.map { QRCodeGenerator.generateQRCode(from: $0) }
}

init() {
Expand All @@ -27,21 +27,22 @@ final class AuthViewModel: ObservableObject {
@MainActor
func setupInitialState() async throws {
state = .none
uri = nil
uriString = nil
let uri = try! await Pair.instance.create()
uriString = uri.absoluteString
try await Auth.instance.request(.stub(), topic: uri.topic)
}

func copyDidPressed() {
UIPasteboard.general.string = uri
UIPasteboard.general.string = uriString
}

func walletDidPressed() {

}

func deeplinkPressed() {
guard let uri = uri else { return }
guard let uri = uriString else { return }
UIApplication.shared.open(URL(string: "showcase://wc?uri=\(uri)")!)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Example/ExampleApp/Common/InputConfig.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Foundation

struct InputConfig {

static var projectId: String {
Expand Down
83 changes: 75 additions & 8 deletions Example/IntegrationTests/Auth/AuthTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ final class AuthTests: XCTestCase {
var appAuthClient: AuthClient!
var walletAuthClient: AuthClient!
let prvKey = Data(hex: "462c1dad6832d7d96ccf87bd6a686a4110e114aaaebd5512e552c0e3a87b480f")
let eip1271Signature = "0xc1505719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c"
private var publishers = [AnyCancellable]()

override func setUp() {
let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")!
setupClients()
}

(appPairingClient, appAuthClient) = makeClients(prefix: "🤖 App")
(walletPairingClient, walletAuthClient) = makeClients(prefix: "🐶 Wallet", account: walletAccount)
private func setupClients(address: String = "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf", iatProvider: IATProvider = DefaultIATProvider()) {
let walletAccount = Account(chainIdentifier: "eip155:1", address: address)!
(appPairingClient, appAuthClient) = makeClients(prefix: "🤖 App", iatProvider: iatProvider)
(walletPairingClient, walletAuthClient) = makeClients(prefix: "🐶 Wallet", account: walletAccount, iatProvider: iatProvider)
}

func makeClients(prefix: String, account: Account? = nil) -> (PairingClient, AuthClient) {
func makeClients(prefix: String, account: Account? = nil, iatProvider: IATProvider) -> (PairingClient, AuthClient) {
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
let keychain = KeychainStorageMock()
let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger)
Expand All @@ -45,11 +49,13 @@ final class AuthTests: XCTestCase {
let authClient = AuthClientFactory.create(
metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
account: account,
projectId: InputConfig.projectId,
logger: logger,
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
networkingClient: networkingClient,
pairingRegisterer: pairingClient)
pairingRegisterer: pairingClient,
iatProvider: iatProvider)

return (pairingClient, authClient)
}
Expand All @@ -66,15 +72,16 @@ final class AuthTests: XCTestCase {
wait(for: [requestExpectation], timeout: InputConfig.defaultTimeout)
}

func testRespondSuccess() async {
func testEIP191RespondSuccess() async {
let responseExpectation = expectation(description: "successful response delivered")
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)

try! await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signature = try! MessageSigner().sign(message: request.message, privateKey: prvKey)
let signer = MessageSignerFactory.create(projectId: InputConfig.projectId)
let signature = try! signer.sign(message: request.message, privateKey: prvKey, type: .eip191)
try! await walletAuthClient.respond(requestId: request.id, signature: signature)
}
}
Expand All @@ -87,6 +94,60 @@ final class AuthTests: XCTestCase {
wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}

func testEIP1271RespondSuccess() async {
setupClients(address: "0x2faf83c542b68f1b4cdc0e770e8cb9f567b08f71", iatProvider: IATProviderMock())

let responseExpectation = expectation(description: "successful response delivered")
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams(
domain: "localhost",
chainId: "eip155:1",
nonce: "1665443015700",
aud: "http://localhost:3000/",
nbf: nil,
exp: "2022-10-11T23:03:35.700Z",
statement: nil,
requestId: nil,
resources: nil
), topic: uri.topic)

try! await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
try! await walletAuthClient.respond(requestId: request.id, signature: signature)
}
}
.store(in: &publishers)
appAuthClient.authResponsePublisher.sink { (_, result) in
guard case .success = result else { XCTFail(); return }
responseExpectation.fulfill()
}
.store(in: &publishers)
wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}

func testEIP191RespondError() async {
let responseExpectation = expectation(description: "error response delivered")
let uri = try! await appPairingClient.create()
try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)

try! await walletPairingClient.pair(uri: uri)
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
try! await walletAuthClient.respond(requestId: request.id, signature: signature)
}
}
.store(in: &publishers)
appAuthClient.authResponsePublisher.sink { (_, result) in
guard case let .failure(error) = result, error == .signatureVerificationFailed else { XCTFail(); return }
responseExpectation.fulfill()
}
.store(in: &publishers)
wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}

func testUserRespondError() async {
let responseExpectation = expectation(description: "error response delivered")
let uri = try! await appPairingClient.create()
Expand Down Expand Up @@ -117,7 +178,7 @@ final class AuthTests: XCTestCase {
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b"
let cacaoSignature = CacaoSignature(t: "eip191", s: invalidSignature)
let cacaoSignature = CacaoSignature(t: .eip191, s: invalidSignature)
try! await walletAuthClient.respond(requestId: request.id, signature: cacaoSignature)
}
}
Expand All @@ -131,3 +192,9 @@ final class AuthTests: XCTestCase {
wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}
}

private struct IATProviderMock: IATProvider {
var iat: String {
return "2022-10-10T23:03:35.700Z"
}
}
4 changes: 2 additions & 2 deletions Example/IntegrationTests/Chat/RegistryTests.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import XCTest
import WalletConnectRelay
import WalletConnectNetworking
import WalletConnectKMS
import WalletConnectUtils
@testable import Chat

final class RegistryTests: XCTestCase {

func testRegistry() async throws {
let client = HTTPClient(host: "keys.walletconnect.com")
let client = HTTPNetworkClient(host: "keys.walletconnect.com")
let registry = KeyserverRegistryProvider(client: client)
let account = Account("eip155:1:" + Data.randomBytes(count: 16).toHexString())!
let pubKey = SigningPrivateKey().publicKey.hexRepresentation
Expand Down
7 changes: 3 additions & 4 deletions Example/IntegrationTests/Pairing/PairingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ final class PairingTests: XCTestCase {
}.store(in: &publishers)
wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}

func testResponseErrorForMethodUnregistered() async {
(appPairingClient, appPushClient) = makeClients(prefix: "🤖 App")
walletPairingClient = makePairingClient(prefix: "🐶 Wallet")

let expectation = expectation(description: "wallet responds unsupported method for unregistered method")

appPushClient.responsePublisher.sink { (id, response) in
appPushClient.responsePublisher.sink { (_, response) in
XCTAssertEqual(response, .failure(WalletConnectPairing.PairError(code: 10001)!))
expectation.fulfill()
}.store(in: &publishers)
Expand All @@ -144,7 +144,6 @@ final class PairingTests: XCTestCase {
}

func testDisconnect() {
//TODO
// TODO
}
}

3 changes: 1 addition & 2 deletions Example/IntegrationTests/Sign/SignClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ final class SignClientTests: XCTestCase {
let pairingTopic = dapp.client.getPairings().first!.topic
if !initiatedSecondSession {
Task(priority: .high) {
let _ = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces, topic: pairingTopic)
_ = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces, topic: pairingTopic)
}
initiatedSecondSession = true
}
Expand Down Expand Up @@ -314,7 +314,6 @@ final class SignClientTests: XCTestCase {
wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}


func testSuccessfulSessionExtend() async {
let expectation = expectation(description: "Dapp extends session")

Expand Down
5 changes: 3 additions & 2 deletions Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Foundation
import Chat
import WalletConnectKMS
import WalletConnectNetworking
import WalletConnectRelay
import WalletConnectKMS
import WalletConnectUtils

class ChatFactory {

static func create() -> ChatClient {
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.showcase")
let client = HTTPClient(host: "keys.walletconnect.com")
let client = HTTPNetworkClient(host: "keys.walletconnect.com")
let registry = KeyserverRegistryProvider(client: client)
return ChatClientFactory.create(
registry: registry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ final class AuthRequestInteractor {

func approve(request: AuthRequest) async throws {
let privateKey = Data(hex: "e56da0e170b5e09a8bb8f1b693392c7d56c3739a9c75740fbc558a2877868540")
let signer = MessageSigner()
let signature = try signer.sign(message: request.message, privateKey: privateKey)
let signer = MessageSignerFactory.create()
let signature = try signer.sign(message: request.message, privateKey: privateKey, type: .eip191)
try await Auth.instance.respond(requestId: request.id, signature: signature)
}

Expand Down
1 change: 0 additions & 1 deletion Example/UITests/Regression/RegressionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class PairingTests: XCTestCase {
engine.routing.launch(app: .wallet, clean: true)
}


/// Check pairing proposal approval via QR code or uri
/// - TU001
func test01PairingCreation() {
Expand Down
3 changes: 1 addition & 2 deletions Sources/Auth/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,15 @@ public class Auth {

/// Auth client instance
public static var instance: AuthClient = {

return AuthClientFactory.create(
metadata: Pair.metadata,
account: config?.account,
projectId: Networking.projectId,
networkingClient: Networking.interactor,
pairingRegisterer: Pair.registerer
)
}()


private static var config: Config?

private init() { }
Expand Down
10 changes: 5 additions & 5 deletions Sources/Auth/AuthClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import WalletConnectNetworking

public struct AuthClientFactory {

public static func create(metadata: AppMetadata, account: Account?, networkingClient: NetworkingInteractor, pairingRegisterer: PairingRegisterer) -> AuthClient {
public static func create(metadata: AppMetadata, account: Account?, projectId: String, networkingClient: NetworkingInteractor, pairingRegisterer: PairingRegisterer) -> AuthClient {
let logger = ConsoleLogger(loggingLevel: .off)
let keyValueStorage = UserDefaults.standard
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
return AuthClientFactory.create(metadata: metadata, account: account, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, networkingClient: networkingClient, pairingRegisterer: pairingRegisterer)
return AuthClientFactory.create(metadata: metadata, account: account, projectId: projectId, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, networkingClient: networkingClient, pairingRegisterer: pairingRegisterer, iatProvider: DefaultIATProvider())
}

static func create(metadata: AppMetadata, account: Account?, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkingClient: NetworkingInteractor, pairingRegisterer: PairingRegisterer) -> AuthClient {
static func create(metadata: AppMetadata, account: Account?, projectId: String, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkingClient: NetworkingInteractor, pairingRegisterer: PairingRegisterer, iatProvider: IATProvider) -> AuthClient {
let kms = KeyManagementService(keychain: keychainStorage)
let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
let messageFormatter = SIWEMessageFormatter()
let appRequestService = AppRequestService(networkingInteractor: networkingClient, kms: kms, appMetadata: metadata, logger: logger)
let messageSigner = MessageSigner(signer: Signer())
let appRequestService = AppRequestService(networkingInteractor: networkingClient, kms: kms, appMetadata: metadata, logger: logger, iatProvader: iatProvider)
let messageSigner = MessageSignerFactory.create(projectId: projectId)
let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: messageSigner, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter)
let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history)
let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, messageFormatter: messageFormatter, address: account?.address, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer)
Expand Down
2 changes: 0 additions & 2 deletions Sources/Auth/AuthProtocolMethod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ struct AuthRequestProtocolMethod: ProtocolMethod {
let responseConfig = RelayConfig(tag: 3001, prompt: false, ttl: 86400)
}


struct PairingPingProtocolMethod: ProtocolMethod {
let method: String = "wc_pairingPing"

Expand All @@ -18,7 +17,6 @@ struct PairingPingProtocolMethod: ProtocolMethod {
let responseConfig = RelayConfig(tag: 1003, prompt: false, ttl: 30)
}


struct PairingDeleteProtocolMethod: ProtocolMethod {
let method: String = "wc_pairingDelete"

Expand Down
8 changes: 5 additions & 3 deletions Sources/Auth/Services/App/AppRequestService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,25 @@ actor AppRequestService {
private let appMetadata: AppMetadata
private let kms: KeyManagementService
private let logger: ConsoleLogging
private let iatProvader: IATProvider

init(networkingInteractor: NetworkInteracting,
kms: KeyManagementService,
appMetadata: AppMetadata,
logger: ConsoleLogging) {
logger: ConsoleLogging,
iatProvader: IATProvider) {
self.networkingInteractor = networkingInteractor
self.kms = kms
self.appMetadata = appMetadata
self.logger = logger
self.iatProvader = iatProvader
}

func request(params: RequestParams, topic: String) async throws {
let pubKey = try kms.createX25519KeyPair()
let responseTopic = pubKey.rawRepresentation.sha256().toHexString()
let requester = AuthRequestParams.Requester(publicKey: pubKey.hexRepresentation, metadata: appMetadata)
let issueAt = ISO8601DateFormatter().string(from: Date())
let payload = AuthPayload(requestParams: params, iat: issueAt)
let payload = AuthPayload(requestParams: params, iat: iatProvader.iat)
let params = AuthRequestParams(requester: requester, payloadParams: payload)
let request = RPCRequest(method: "wc_authRequest", params: params)
try kms.setPublicKey(publicKey: pubKey, for: responseTopic)
Expand Down
19 changes: 14 additions & 5 deletions Sources/Auth/Services/App/AppRespondSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,20 @@ class AppRespondSubscriber {
guard messageFormatter.formatMessage(from: requestPayload.payloadParams, address: address) == message
else { self.onResponse?(requestId, .failure(.messageCompromised)); return }

guard let _ = try? signatureVerifier.verify(signature: cacao.s, message: message, address: address)
else { self.onResponse?(requestId, .failure(.signatureVerificationFailed)); return }

onResponse?(requestId, .success(cacao))

Task(priority: .high) {
do {
try await signatureVerifier.verify(
signature: cacao.s,
message: message,
address: address,
chainId: requestPayload.payloadParams.chainId
)
onResponse?(requestId, .success(cacao))
} catch {
logger.error("Signature verification failed with: \(error.localizedDescription)")
onResponse?(requestId, .failure(.signatureVerificationFailed))
}
}
}.store(in: &publishers)
}
}
Loading

0 comments on commit 3fb5c2a

Please sign in to comment.