diff --git a/Package.resolved b/Package.resolved index 2b4c38f..978df43 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "AnyCodable", + "repositoryURL": "https://github.com/Flight-School/AnyCodable", + "state": { + "branch": null, + "revision": "862808b2070cd908cb04f9aafe7de83d35f81b05", + "version": "0.6.7" + } + }, { "package": "BigInt", "repositoryURL": "https://github.com/attaswift/BigInt.git", @@ -19,6 +28,24 @@ "version": "1.0.1" } }, + { + "package": "CustomAuth", + "repositoryURL": "https://github.com/torusresearch/customauth-swift-sdk", + "state": { + "branch": null, + "revision": "60b48acb161fb4341c353c27bc6dd96af76887ff", + "version": "10.0.1" + } + }, + { + "package": "FetchNodeDetails", + "repositoryURL": "https://github.com/torusresearch/fetch-node-details-swift.git", + "state": { + "branch": null, + "revision": "22bfadf7491d77a0bc1953af002cadbd61383e7d", + "version": "6.0.2" + } + }, { "package": "GenericJSON", "repositoryURL": "https://github.com/iwill/generic-json-swift", @@ -29,12 +56,66 @@ } }, { - "package": "secp256k1", + "package": "jwt-kit", + "repositoryURL": "https://github.com/vapor/jwt-kit.git", + "state": { + "branch": null, + "revision": "c2595b9ad7f512d7f334830b4df1fed6e917946a", + "version": "4.13.4" + } + }, + { + "package": "JWTDecode", + "repositoryURL": "https://github.com/auth0/JWTDecode.swift.git", + "state": { + "branch": null, + "revision": "58af7278797871e460d79496621b3e5366b865b2", + "version": "3.1.0" + } + }, + { + "package": "KeychainSwift", + "repositoryURL": "https://github.com/evgenyneu/keychain-swift.git", + "state": { + "branch": null, + "revision": "d108a1fa6189e661f91560548ef48651ed8d93b9", + "version": "20.0.0" + } + }, + { + "package": "mpc-core-kit-swift", + "repositoryURL": "https://github.com/tkey/mpc-core-kit-swift", + "state": { + "branch": "feat/signer", + "revision": "c8be9779450b818285379daf8a30e1621cf872d9", + "version": null + } + }, + { + "package": "secp256k1.swift", "repositoryURL": "https://github.com/GigaBitcoin/secp256k1.swift.git", "state": { "branch": null, - "revision": "1a14e189def5eaa92f839afdd2faad8e43b61a6e", - "version": "0.12.2" + "revision": "9683e8e311c76d8114cd308b697dad2f9fc58fed", + "version": "0.17.0" + } + }, + { + "package": "SessionManager", + "repositoryURL": "https://github.com/Web3Auth/session-manager-swift.git", + "state": { + "branch": null, + "revision": "20cc7bff065d7fe53164d17e7714a3f17d4cea2a", + "version": "4.0.2" + } + }, + { + "package": "SingleFactorAuth", + "repositoryURL": "https://github.com/Web3Auth/single-factor-auth-swift", + "state": { + "branch": null, + "revision": "73c5066d369c7d2aa1057c56a89b7f1dcdda3270", + "version": "5.0.0" } }, { @@ -42,8 +123,8 @@ "repositoryURL": "https://github.com/socketio/socket.io-client-swift", "state": { "branch": null, - "revision": "af5ce97b755d964235348d96f6db5cbdcbe334a5", - "version": "16.0.1" + "revision": "175da8b5156f6b132436f0676cc84c2f6a766b6e", + "version": "16.1.0" } }, { @@ -51,8 +132,8 @@ "repositoryURL": "https://github.com/daltoniam/Starscream", "state": { "branch": null, - "revision": "df8d82047f6654d8e4b655d1b1525c64e1059d21", - "version": "4.0.4" + "revision": "ac6c0fc9da221873e01bd1a0d4818498a71eef33", + "version": "4.0.6" } }, { @@ -60,8 +141,8 @@ "repositoryURL": "https://github.com/apple/swift-atomics.git", "state": { "branch": null, - "revision": "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version": "1.1.0" + "revision": "cd142fd2f64be2100422d658e7411e39489da985", + "version": "1.2.0" } }, { @@ -69,8 +150,26 @@ "repositoryURL": "https://github.com/apple/swift-collections.git", "state": { "branch": null, - "revision": "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version": "1.0.4" + "revision": "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version": "1.1.0" + } + }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", + "state": { + "branch": null, + "revision": "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e", + "version": "3.4.0" + } + }, + { + "package": "swift-http-types", + "repositoryURL": "https://github.com/apple/swift-http-types", + "state": { + "branch": null, + "revision": "9bee2fdb79cc740081abd8ebd80738063d632286", + "version": "1.1.0" } }, { @@ -78,8 +177,8 @@ "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "532d8b529501fb73a2455b179e0bbb6d49b652ed", - "version": "1.5.3" + "revision": "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version": "1.5.4" } }, { @@ -87,8 +186,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "cf281631ff10ec6111f2761052aa81896a83a007", - "version": "2.58.0" + "revision": "359c461e5561d22c6334828806cc25d759ca7aa6", + "version": "2.65.0" } }, { @@ -96,8 +195,17 @@ "repositoryURL": "https://github.com/apple/swift-nio-extras.git", "state": { "branch": null, - "revision": "0e0d0aab665ff1a0659ce75ac003081f2b1c8997", - "version": "1.19.0" + "revision": "a3b640d7dc567225db7c94386a6e71aded1bfa63", + "version": "1.22.0" + } + }, + { + "package": "swift-nio-http2", + "repositoryURL": "https://github.com/apple/swift-nio-http2.git", + "state": { + "branch": null, + "revision": "c6afe04165c865faaa687b42c32ed76dfcc91076", + "version": "1.31.0" } }, { @@ -105,8 +213,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "320bd978cceb8e88c125dcbb774943a92f6286e9", - "version": "2.25.0" + "revision": "7c381eb6083542b124a6c18fae742f55001dc2b5", + "version": "2.26.0" } }, { @@ -114,8 +222,35 @@ "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", "state": { "branch": null, - "revision": "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58", - "version": "1.19.0" + "revision": "38ac8221dd20674682148d6451367f89c2652980", + "version": "1.21.0" + } + }, + { + "package": "swift-system", + "repositoryURL": "https://github.com/apple/swift-system.git", + "state": { + "branch": null, + "revision": "f9266c85189c2751589a50ea5aec72799797e471", + "version": "1.3.0" + } + }, + { + "package": "tkey-mpc-swift", + "repositoryURL": "https://github.com/tkey/tkey-mpc-swift", + "state": { + "branch": null, + "revision": "8fe0cc8d5cc2ad5d38bf068bc29f6af7e3146d08", + "version": "3.0.0" + } + }, + { + "package": "TorusUtils", + "repositoryURL": "https://github.com/torusresearch/torus-utils-swift.git", + "state": { + "branch": null, + "revision": "ff85c3e96bfa29013309b487875c4d9383e4ac80", + "version": "8.1.1" } }, { @@ -123,8 +258,8 @@ "repositoryURL": "https://github.com/torusresearch/tss-client-swift.git", "state": { "branch": null, - "revision": "40246d5e3ff1d2c97d41846576f7a81d58858981", - "version": "4.0.0" + "revision": "3beedc78716d9681aaa1dbe110cdfd33090a8fff", + "version": "4.0.2" } }, { @@ -132,8 +267,8 @@ "repositoryURL": "https://github.com/argentlabs/web3.swift", "state": { "branch": null, - "revision": "8ca33e700ed8de6137a0e1471017aa3b3c8de0db", - "version": "1.6.0" + "revision": "1e75f98a5738c470b23bbfffa9314e9f788df76b", + "version": "1.6.1" } }, { @@ -141,8 +276,8 @@ "repositoryURL": "https://github.com/vapor/websocket-kit.git", "state": { "branch": null, - "revision": "53fe0639a98903858d0196b699720decb42aee7b", - "version": "2.14.0" + "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", + "version": "2.15.0" } } ] diff --git a/Package.swift b/Package.swift index dcda146..cf57c39 100644 --- a/Package.swift +++ b/Package.swift @@ -5,17 +5,23 @@ import PackageDescription let package = Package( name: "Web3SwiftMpcProvider", - platforms: [.iOS(.v13), .macOS(.v11)], + platforms: [.iOS(.v14), .macOS(.v11)], products: [ .library( name: "Web3SwiftMpcProvider", targets: ["Web3SwiftMpcProvider"]), + .library( + name: "MPCEthereumProvider", + targets: ["MPCEthereumProvider"]), ], dependencies: [ .package(url: "https://github.com/argentlabs/web3.swift", from:"1.6.0"), .package(url: "https://github.com/torusresearch/tss-client-swift.git", from: "4.0.0"), .package(url: "https://github.com/tkey/curvelib.swift", from: "1.0.1"), + .package(url: "https://github.com/tkey/mpc-core-kit-swift", branch: "feat/signer"), + .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"), + ], targets: [ .target( @@ -23,9 +29,20 @@ let package = Package( dependencies: ["web3.swift", .product(name: "tssClientSwift", package: "tss-client-swift"), .product(name: "curveSecp256k1", package: "curvelib.swift")], path: "Sources/Web3SwiftMpcProvider" ), + .target( + name: "MPCEthereumProvider", + dependencies: ["web3.swift", + .product(name: "curveSecp256k1", package: "curvelib.swift"), + ], + path: "Sources/MPCEthereumProvider" + ), + .testTarget( name: "Web3SwiftMpcProviderTests", - dependencies: ["Web3SwiftMpcProvider"], + dependencies: ["Web3SwiftMpcProvider", "MPCEthereumProvider", + .product(name: "mpc-core-kit-swift", package: "mpc-core-kit-swift"), + .product(name: "JWTKit", package: "jwt-kit") + ], path: "Tests"), ], swiftLanguageVersions: [.v5] diff --git a/Sources/MPCEthereumProvider/MPCEthereumProvider.swift b/Sources/MPCEthereumProvider/MPCEthereumProvider.swift new file mode 100644 index 0000000..b07e3cf --- /dev/null +++ b/Sources/MPCEthereumProvider/MPCEthereumProvider.swift @@ -0,0 +1,193 @@ +// +// File.swift +// +// +// Created by CW Lee on 17/05/2024. +// + +import BigInt +import Foundation +import web3 + +#if canImport(curveSecp256k1) +import curveSecp256k1 +#endif + + +public protocol EvmSigner { + func sign( message: Data ) throws -> Data + var publicKey : Data { get } +} + +enum CustomSigningError: Error { + case generalError(error: String = "") + + public var errorDescription: String { + switch self { + case let .generalError(err): + return err + } + } +} + +enum EthereumSignerError: Error { + case emptyRawTransaction + case unknownError +} + +public class MPCEthereumProvider : EthereumAccountProtocol { + let signer: EvmSigner + + public init( evmSigner: EvmSigner) { + signer = evmSigner + } + + public var address: web3.EthereumAddress { + // try async + return EthereumAddress(KeyUtil.generateAddress(from: self.signer.publicKey ).toChecksumAddress()) + } + + + public func sign(message: Data) throws -> Data { + return try self.signer.sign(message: message) + } + + /// Signs using provided Data + /// + /// - Parameters: + /// - data : Data to be signed + /// + /// - Returns: `Data` + /// + /// - Throws: On signing failure + public func sign(data: Data) throws -> Data { + let hash = try keccak256(data: data) + let signature = try sign(message: hash) + return signature + } + + /// Signs using provided Hex String + /// + /// - Parameters: + /// - hex : Hex string to be signed + /// + /// - Returns: `Data` + /// + /// - Throws: On signing failure + public func sign(hex: String) throws -> Data { + if let data = Data(hex: hex) { + return try sign(data: data) + } else { + throw EthereumAccountError.signError + } + } + + /// Signs using provided hash + /// + /// - Parameters: + /// - hash : Hash to be used for signing + /// + /// - Returns: `Data` + /// + /// - Throws: On signing failure + public func sign(hash: String) throws -> Data { + if let data = hash.web3.hexData { + return try sign(message: data) + } else { + throw EthereumAccountError.signError + } + } + + /// Signs using provided message string + /// + /// - Parameters: + /// - message : message to be used for signing + /// + /// - Returns: `Data` + /// + /// - Throws: On signing failure + public func sign(message: String) throws -> Data { + if let data = message.data(using: .utf8) { + return try sign(data: data) + } else { + throw EthereumAccountError.signError + } + } + + /// Signs using provided message data, prefixing the data first + /// + /// - Parameters: + /// - message : message to be used for signing + /// + /// - Returns: `String` + /// + /// - Throws: On signing failure + public func signMessage(message: Data) throws -> String { + let prefix = "\u{19}Ethereum Signed Message:\n\(String(message.count))" + guard var data = prefix.data(using: .ascii) else { + throw EthereumAccountError.signError + } + data.append(message) + let hash = data.web3.keccak256 + + var signed = try sign(message: hash) + + // Check last char (v) + guard var last = signed.popLast() else { + throw EthereumAccountError.signError + } + + if last < 27 { + last += 27 + } + + signed.append(last) + return signed.web3.hexString + } + + /// Signs using provided structured typed message (EIP712) + /// + /// - Parameters: + /// - message : message to be used for signing + /// + /// - Returns: `String` + /// + /// - Throws: On signing failure + public func signMessage(message: TypedData) throws -> String { + let hash = try message.signableHash() + + var signed = try sign(message: hash) + + // Check last char (v) + guard var last = signed.popLast() else { + throw EthereumAccountError.signError + } + + if last < 27 { + last += 27 + } + + signed.append(last) + return signed.web3.hexString + } + + /// Signs an ethereum transaction + /// + /// - Parameters: + /// - transaction : Transaction to be signed + /// + /// - Returns: `SignedTransaction` + /// + /// - Throws: On signing failure + public func sign(transaction: EthereumTransaction) throws -> SignedTransaction { + guard let raw = transaction.raw else { + throw EthereumSignerError.emptyRawTransaction + } + + // hash and sign data + let signed = try sign(data: raw) + + return SignedTransaction(transaction: transaction, signature: signed) + } + +} diff --git a/Sources/Web3SwiftMpcProvider/EthereumTssAccount.swift b/Sources/Web3SwiftMpcProvider/EthereumTssAccount.swift index 70c5729..91260aa 100644 --- a/Sources/Web3SwiftMpcProvider/EthereumTssAccount.swift +++ b/Sources/Web3SwiftMpcProvider/EthereumTssAccount.swift @@ -1,8 +1,10 @@ import BigInt import Foundation -import curveSecp256k1 import tssClientSwift import web3 +#if canImport(curveSecp256k1) +import curveSecp256k1 +#endif enum CustomSigningError: Error { case generalError(error: String = "") diff --git a/Tests/Web3SwiftMpcProviderTests/MpcProviderTests.swift b/Tests/Web3SwiftMpcProviderTests/MpcProviderTests.swift index 2fe49b8..46b467e 100644 --- a/Tests/Web3SwiftMpcProviderTests/MpcProviderTests.swift +++ b/Tests/Web3SwiftMpcProviderTests/MpcProviderTests.swift @@ -2,6 +2,10 @@ import BigInt import web3 import Web3SwiftMpcProvider import XCTest +import mpc_core_kit_swift + +import MPCEthereumProvider + final class Web3SwiftMpcProviderTests: XCTestCase { let example1 = """ @@ -117,4 +121,45 @@ final class Web3SwiftMpcProviderTests: XCTestCase { let tssAccount = try EthereumTssAccount(params: params) let _ = try tssAccount.signMessage(message: typedData) } + + + func resetMPC(email: String, verifier: String, clientId: String) async throws { + var coreKitInstance = MpcCoreKit(web3AuthClientId: clientId, web3AuthNetwork: .SAPPHIRE_DEVNET, localStorage: MemoryStorage()) + + let data = try mockLogin2(email: email) + let token = data + + + let keyDetails = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) + try await coreKitInstance.resetAccount() + } + + func testMpcProviderSigning() async throws { + + let email = "testiosEmail004" + let verifier = "torus-test-health" + let clientId = "torus-test-health" + + // reset account for testing + try await resetMPC(email: email, verifier: verifier, clientId: clientId) + + + // setup mpc + let memoryStorage = MemoryStorage() + var coreKitInstance = MpcCoreKit( web3AuthClientId: clientId, web3AuthNetwork: .SAPPHIRE_DEVNET, localStorage: memoryStorage) + + let data = try mockLogin2(email: email) + let token = data + + let _ = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) + + // + let provider = MPCEthereumProvider(evmSigner: coreKitInstance ) + let msg = "hello world" + let result = try provider.sign(message: msg) + + let typedData = try decoder.decode(TypedData.self, from: example1) + let typedDataResult = try provider.signMessage(message: typedData) + print(typedDataResult) + } } diff --git a/Tests/Web3SwiftMpcProviderTests/helper.swift b/Tests/Web3SwiftMpcProviderTests/helper.swift new file mode 100644 index 0000000..6e7b9a0 --- /dev/null +++ b/Tests/Web3SwiftMpcProviderTests/helper.swift @@ -0,0 +1,86 @@ +// +// File.swift +// +// +// Created by CW Lee on 20/05/2024. +// + +import Foundation +import mpc_core_kit_swift +import JWTKit + +public class MemoryStorage : ILocalStorage { + var memory : [String:Data] = [:] + + public func get(key: String) async throws -> Data { + guard let result = memory[key] else { + return Data() + } + return result + } + + public func set(key: String, payload: Data) async throws { + memory.updateValue(payload, forKey: key) + } +} + + +func mockLogin2 (email:String) throws -> String { + + let verifierPrivateKeyForSigning = + """ + -----BEGIN PRIVATE KEY----- + MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCD7oLrcKae+jVZPGx52Cb/lKhdKxpXjl9eGNa1MlY57A== + -----END PRIVATE KEY----- + """ + + do { + let signers = JWTSigners() + let keys = try ECDSAKey.private(pem: verifierPrivateKeyForSigning) + signers.use(.es256(key: keys)) + + // Parses the JWT and verifies its signature. + let today = Date() + let modifiedDate = Calendar.current.date(byAdding: .hour, value: 1, to: today)! + + let emailComponent = email.components(separatedBy: "@")[0] + let subject = "email|" + emailComponent + + let payload = TestPayload(subject: SubjectClaim(stringLiteral: subject), expiration: ExpirationClaim(value: modifiedDate), audience: "torus-key-test", isAdmin: false, emailVerified: true, issuer: "torus-key-test", iat: IssuedAtClaim(value: Date()), email: email) + let jwt = try signers.sign(payload) + return jwt + } catch { + throw error + } + +} + + + +// JWT payload structure. +struct TestPayload: JWTPayload, Equatable { + enum CodingKeys: String, CodingKey { + case subject = "sub" + case expiration = "exp" + case isAdmin = "admin" + case emailVerified = "email_verified" + case issuer = "iss" + case iat + case email + case audience = "aud" + } + + var subject: SubjectClaim + var expiration: ExpirationClaim + var audience: AudienceClaim + var isAdmin: Bool + let emailVerified: Bool + var issuer: IssuerClaim + var iat: IssuedAtClaim + var email: String + + // call its verify method. + func verify(using signer: JWTSigner) throws { + try expiration.verifyNotExpired() + } +} diff --git a/Tests/Web3SwiftMpcProviderTests/signer.swift b/Tests/Web3SwiftMpcProviderTests/signer.swift new file mode 100644 index 0000000..8eedb56 --- /dev/null +++ b/Tests/Web3SwiftMpcProviderTests/signer.swift @@ -0,0 +1,59 @@ +// +// File.swift +// +// +// Created by CW Lee on 20/05/2024. +// + +import Foundation + +import Foundation +import mpc_core_kit_swift +import MPCEthereumProvider + +//public protocol IBticoinSigner { +// func sign( message: Data ) -> Data +//// func schnorrSign(message: Data, publicKey: Data) -> Data +// var publicKey : Data { get } +//} +// +// +//extension MpcCoreKit : IBticoinSigner { +// public func sign(message: Data) -> Data { +// let data = try? self.tssSign(message: message) +// return data ?? Data([]) +// } +// +// +// // MPC do not support shnorrSign yet +// // return empty data to fullfill signer interface requirement +//// public func schnorrSign(message: Data, publicKey: Data) -> Data { +//// return Data() +//// } +// +// public var publicKey: Data { +// return self.getTssPubKey() +// } +// +//} + + +// EVM signer +//public protocol ISigner { +// func sign( message: Data ) -> Data +// var publicKey : Data { get } +//} + + +extension MpcCoreKit : EvmSigner { + public func sign(message: Data) throws -> Data { + let data = try self.tssSign(message: message) + return data + } + + public var publicKey: Data { + return self.getTssPubKey().suffix(64) + } + +} +