diff --git a/Package.resolved b/Package.resolved index e3663bf..36c33be 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,7 +41,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/torusresearch/fetch-node-details-swift.git", "state" : { - "revision" : "c8d0121fed49ce761f5946bc30e1aafbe1ac5acb", + "revision" : "22bfadf7491d77a0bc1953af002cadbd61383e7d", "version" : "6.0.2" } }, @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tkey/tkey-mpc-swift", "state" : { - "revision" : "8fe0cc8d5cc2ad5d38bf068bc29f6af7e3146d08", - "version" : "3.0.0" + "revision" : "2df2d0a6c51afc76909028eb8542fe78e565d106", + "version" : "3.0.1" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/torusresearch/torus-utils-swift.git", "state" : { - "revision" : "ff85c3e96bfa29013309b487875c4d9383e4ac80", - "version" : "8.1.1" + "revision" : "608c28404c506983bfec7bbd957632fc0544db8c", + "version" : "8.1.2" } }, { diff --git a/Package.swift b/Package.swift index 45d750f..20f6a38 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "mpc-core-kit-swift", - platforms: [.iOS(.v14), .macOS(.v11)], + platforms: [ .iOS(.v14) ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( @@ -15,9 +15,10 @@ let package = Package( dependencies: [ .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"), .package(url: "https://github.com/torusresearch/tss-client-swift.git", from: "4.0.0"), - .package(url: "https://github.com/tkey/tkey-mpc-swift", from: "3.0.0"), + .package(url: "https://github.com/tkey/tkey-mpc-swift", from: "3.0.1"), .package(url: "https://github.com/torusresearch/customauth-swift-sdk", from: "10.0.1"), - .package(url: "https://github.com/Web3Auth/single-factor-auth-swift", from: "5.0.0") + .package(url: "https://github.com/Web3Auth/single-factor-auth-swift", from: "5.0.0"), + .package(url: "https://github.com/auth0/JWTDecode.swift", from: "3.1.0") ], targets: [ @@ -30,6 +31,7 @@ let package = Package( .product(name: "SingleFactorAuth", package: "single-factor-auth-swift"), .product(name: "tkey", package: "tkey-mpc-swift" ), .product(name: "tssClientSwift", package: "tss-client-swift" ), + .product(name: "JWTDecode", package: "JWTDecode.swift") ] ), .testTarget( diff --git a/Sources/mpc-core-kit-swift/CoreKitError.swift b/Sources/mpc-core-kit-swift/CoreKitError.swift new file mode 100644 index 0000000..ec46ed2 --- /dev/null +++ b/Sources/mpc-core-kit-swift/CoreKitError.swift @@ -0,0 +1,90 @@ +import Foundation + +public enum CoreKitError: Error { + case invalidResult + case invalidInput + case notFound(msg: String) + case factorKeyUnavailable + case metadataPubKeyUnavailable + case shareIndexNotFound + case notLoggedIn + case notInitialized + case keyDetailsNotFound + case invalidVerifierOrVerifierID + case invalidNode + case invalidMetadataEndpoint + case invalidPostboxKey + case invalidSessionData + case invalidTKey + case nodeDetailsMissing + case invalidMetadataUrl + case invalidOAuthKey + case invalidHashKey + case invalidAuthSignatures + case invalidFactorKey + case factorKeyAndFactorPubMismatch + case invalidMetadataPubKey + case currentFactorNotHashFactor + case requireUncompressedPublicKey + case invalidStore + case noTssTags + + public var errorDescription: String { + switch self { + case .invalidResult: + return "Invalid Result" + case let .notFound(msg: msg): + return msg + case .metadataPubKeyUnavailable: + return "Metadata public key is not available" + case .invalidInput: + return "Invalid input" + case .factorKeyUnavailable: + return "Factor key unavailable" + case .shareIndexNotFound: + return "Share index not found" + case .notLoggedIn: + return "User is not logged in" + case .notInitialized: + return "User is not initialized" + case .keyDetailsNotFound: + return "Key details not found" + case .invalidVerifierOrVerifierID: + return "Invalid verifier or verifierID" + case .invalidNode: + return "Invalid verifier or verifierID" + case .invalidMetadataEndpoint: + return "Invalid verifier or verifierID" + case .invalidPostboxKey: + return "Invalid postbox key" + case .invalidSessionData: + return "Invalid session data" + case .invalidTKey: + return "Invalid tKey" + case .nodeDetailsMissing: + return "Node details absent" + case .invalidMetadataUrl: + return "Invalid metadata url" + case .invalidOAuthKey: + return "Invalid OAuthKey" + case .invalidHashKey: + return "Invalid hash key" + case .invalidAuthSignatures: + return "Invalid auth signatures" + case .invalidFactorKey: + return "Invalid factor key" + case .factorKeyAndFactorPubMismatch: + return "Factor key does not match factor public key" + case .invalidMetadataPubKey: + return "Invalid metadata public key" + case .currentFactorNotHashFactor: + return "Current factor is not hash factor" + case .requireUncompressedPublicKey: + return "Public key needs to be in uncompressed format" + case .invalidStore: + return "Invalid Store" + case .noTssTags: + return "No Tss tags have been set" + } + } +} diff --git a/Sources/mpc-core-kit-swift/CoreKitStorage.swift b/Sources/mpc-core-kit-swift/CoreKitStorage.swift index c831b61..9278f7c 100644 --- a/Sources/mpc-core-kit-swift/CoreKitStorage.swift +++ b/Sources/mpc-core-kit-swift/CoreKitStorage.swift @@ -1,139 +1,61 @@ -// -// File.swift -// -// -// Created by CW Lee on 07/02/2024. -// - import Foundation public class CoreKitStorage { - internal static var instance : CoreKitStorage? ; - - public var storage : ILocalStorage - private var storeKey : String - - public init(storeKey : String, storage: ILocalStorage) { + public var storage: ILocalStorage + private var storeKey: String + + public init(storeKey: String, storage: ILocalStorage) { self.storage = storage self.storeKey = storeKey } - -// static func getInstance( storeKey: String , storage: ILocalStorage? = nil) throws -> CoreKitStorage { -// guard let localInstance = self.instance else { -// guard let lStorage = storage else { -// throw "no storage provided or found" -// } -// let local = CoreKitStorage.init(storeKey: storeKey, storage: lStorage) -// CoreKitStorage.instance = local -// return local -// } -// return localInstance -// } - + public func resetStore() async throws -> Data { - let result = try await self.storage.get(key: self.storeKey) + let result = try await storage.get(key: storeKey) let payload = try JSONSerialization.data(withJSONObject: [:]) - - try await self.storage.set(key: self.storeKey, payload: payload) - + + try await storage.set(key: storeKey, payload: payload) + return result } - - public func toJsonStr() async throws -> String { - let result = try await self.storage.get(key: self.storeKey) + + public func getStringified() async throws -> String { + // TODO: JSONDecoder and appropriate types, if applicable + let result = try await storage.get(key: storeKey) guard let resultStr = String(data: result, encoding: .utf8) else { - throw "invalid store" + throw CoreKitError.invalidStore } return resultStr } - + public func getStore() async throws -> [String: Any] { - let result = try await self.storage.get(key: self.storeKey) + let result = try await storage.get(key: storeKey) if result.isEmpty { return [:] } let store = try JSONSerialization.jsonObject(with: result) as? [String: Any] guard let storeUnwrapped = store else { - return [:] + throw CoreKitError.invalidStore } return storeUnwrapped } - - public func get(key:String) async throws -> T { - let store = try await self.getStore() - + + public func get(key: String) async throws -> T { + let store = try await getStore() guard let item = store[key] as? T else { - throw "key \(key) value not found" + throw CoreKitError.notFound(msg: "key \(key) value not found") } return item } - - public func set(key:String, payload: T) async throws { - var store : [String:Any] = try await self.getStore() - store.updateValue( payload, forKey: key) - + + public func set(key: String, payload: T) async throws { + var store: [String: Any] = try await getStore() + store.updateValue(payload, forKey: key) let jsonData = try JSONSerialization.data(withJSONObject: store) - try await self.storage.set(key: self.storeKey, payload: jsonData) + try await storage.set(key: storeKey, payload: jsonData) } - - public func remove(key:String) async throws { - var store = try await self.getStore() + + public func remove(key: String) async throws { + var store = try await getStore() store[key] = nil let jsonData = try JSONSerialization.data(withJSONObject: store) - try await self.storage.set(key: self.storeKey, payload: jsonData) - } - -} - -// Example implementation of Factor Storage Protocol (Interface) -class DeviceFactorStorage : IFactorStorage { - let storage: CoreKitStorage - - public init(storage: CoreKitStorage) { - self.storage = storage - } - - public func setFactor(metadataPubKey: String, factorKey: String) async throws { - var localMetadata : [String: Any] = [:] - let result : [String: Any]? = try? await self.storage.get(key: metadataPubKey) - if let result = result { - localMetadata = result - } - localMetadata["factorKey"] = factorKey - try await self.storage.set(key: metadataPubKey, payload: localMetadata) - } - - public func getFactor(metadataPubKey: String) async throws -> String { - let localMetadata : [String: Any]? = try? await self.storage.get(key: metadataPubKey) - guard let localMetadata = localMetadata, let deviceFactor = localMetadata["factorKey"] as? String else { - throw "device factor not found" - } - return deviceFactor - } -} - - -extension MpcCoreKit { - public func getDeviceFactor () async throws -> String { - // getMetadataPublicKey compressed - guard let metadataPubKey = self.appState.metadataPubKey else { - throw "metadataPubKey is not available" - } - - let deviceFactorStorage = DeviceFactorStorage(storage: self.coreKitStorage) - return try await deviceFactorStorage.getFactor(metadataPubKey: metadataPubKey) - } - - public func setDeviceFactor ( factorKey: String ) async throws { - guard let metadataPubKey = self.appState.metadataPubKey else { - throw "metadataPubKey is not available" - } - let deviceFactorStorage = DeviceFactorStorage(storage: self.coreKitStorage) - try await deviceFactorStorage.setFactor(metadataPubKey: metadataPubKey, factorKey: factorKey) - } - - internal func resetDeviceFactorStore () async throws { - guard let metadataPubKey = self.appState.metadataPubKey else { - throw "metadataPubKey is not available" - } - try await self.coreKitStorage.set(key: metadataPubKey, payload: [:]) + try await storage.set(key: storeKey, payload: jsonData) } } diff --git a/Sources/mpc-core-kit-swift/DeviceFactorStorage.swift b/Sources/mpc-core-kit-swift/DeviceFactorStorage.swift new file mode 100644 index 0000000..c3b4a00 --- /dev/null +++ b/Sources/mpc-core-kit-swift/DeviceFactorStorage.swift @@ -0,0 +1,25 @@ +import Foundation + +public class DeviceFactorStorage: IFactorStorage { + let storage: CoreKitStorage + + public init(storage: CoreKitStorage) { + self.storage = storage + } + + public func setFactor(metadataPubKey: String, factorKey: String) async throws { + var localMetadata: [String: Any] = try await storage.get(key: metadataPubKey) + + localMetadata["factorKey"] = factorKey + try await storage.set(key: metadataPubKey, payload: localMetadata) + } + + public func getFactor(metadataPubKey: String) async throws -> String { + let localMetadata: [String: Any] = try await storage.get(key: metadataPubKey) + guard let deviceFactor = localMetadata["factorKey"] as? String else { + throw CoreKitError.notFound(msg: "device factor not found") + } + return deviceFactor + } +} + diff --git a/Sources/mpc-core-kit-swift/Helper.swift b/Sources/mpc-core-kit-swift/Helper.swift index 2eb5ff4..0e7a8c2 100644 --- a/Sources/mpc-core-kit-swift/Helper.swift +++ b/Sources/mpc-core-kit-swift/Helper.swift @@ -1,81 +1,54 @@ -// -// File.swift -// -// -// Created by CW Lee on 16/01/2024. -// - -import Foundation -import tssClientSwift -import tkey import BigInt -import curveSecp256k1 import FetchNodeDetails - +import Foundation import SingleFactorAuth +#if canImport(tkey) + import tkey +#endif -func convertPublicKeyFormat ( publicKey: String, outFormat: PublicKeyEncoding ) throws -> String { - let point = try KeyPoint(address: publicKey) - let result = try point.getPublicKey(format: outFormat) - return result -} +#if canImport(curveSecp256k1) + import curveSecp256k1 +#endif + +#if canImport(tssClientSwift) + import tssClientSwift +#endif -public func createCoreKitFactorDescription ( module: FactorDescriptionTypeModule, tssIndex: TssShareType, additional : [String:Any] = [:] ) -> [String: Any] { +public func createCoreKitFactorDescription(module: FactorType, tssIndex: TssShareType, additional: [String: Any] = [:]) -> [String: Any] { var description = additional - - description["module"] = module.toString() - description["tssShareIndex"] = tssIndex.toString() + + description["module"] = module + description["tssShareIndex"] = tssIndex description["dateAdded"] = Date().timeIntervalSince1970 - + return description } -func factorDescriptionToJsonStr ( dataObj: [String: Any] ) throws -> String { +func factorDescriptionToJsonStr(dataObj: [String: Any]) throws -> String { let json = try JSONSerialization.data(withJSONObject: dataObj) guard let jsonStr = String(data: json, encoding: .utf8) else { - throw "Invalid data structure" + throw CoreKitError.invalidResult } return jsonStr } - -public func hashMessage(message: String) throws -> String { - return try TSSHelpers.hashMessage(message: message) -} - -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 convertWeb3AuthNetworkToTorusNetWork ( network: Web3AuthNetwork ) -> TorusNetwork { +func convertWeb3AuthNetworkToTorusNetWork(network: Web3AuthNetwork) -> TorusNetwork { switch network { - case Web3AuthNetwork.SAPPHIRE_DEVNET : return .sapphire(.SAPPHIRE_DEVNET); - case Web3AuthNetwork.SAPPHIRE_MAINNET : return .sapphire(.SAPPHIRE_MAINNET); - case Web3AuthNetwork.MAINNET : return .legacy(.MAINNET); - case Web3AuthNetwork.TESTNET: return .legacy(.TESTNET); - case Web3AuthNetwork.CYAN: return .legacy(.CYAN); - case Web3AuthNetwork.AQUA: return .legacy(.AQUA); - case Web3AuthNetwork.CELESTE: return .legacy(.CELESTE); - case Web3AuthNetwork.CUSTOM(_): return .sapphire(.SAPPHIRE_MAINNET); + case Web3AuthNetwork.SAPPHIRE_DEVNET: return .sapphire(.SAPPHIRE_DEVNET) + case Web3AuthNetwork.SAPPHIRE_MAINNET: return .sapphire(.SAPPHIRE_MAINNET) + case Web3AuthNetwork.MAINNET: return .legacy(.MAINNET) + case Web3AuthNetwork.TESTNET: return .legacy(.TESTNET) + case Web3AuthNetwork.CYAN: return .legacy(.CYAN) + case Web3AuthNetwork.AQUA: return .legacy(.AQUA) + case Web3AuthNetwork.CELESTE: return .legacy(.CELESTE) + case Web3AuthNetwork.CUSTOM: return .sapphire(.SAPPHIRE_MAINNET) } } public extension Web3AuthNetwork { - func toTorusNetwork () -> TorusNetwork{ + func toTorusNetwork() -> TorusNetwork { return convertWeb3AuthNetworkToTorusNetWork(network: self) } } diff --git a/Sources/mpc-core-kit-swift/Interface.swift b/Sources/mpc-core-kit-swift/Interface.swift index 65712de..2c615af 100644 --- a/Sources/mpc-core-kit-swift/Interface.swift +++ b/Sources/mpc-core-kit-swift/Interface.swift @@ -1,178 +1,97 @@ -// -// File.swift -// -// -// Created by CW Lee on 23/01/2024. -// - import Foundation -import tkey import SingleFactorAuth -public protocol ILocalStorage { - func set(key:String, payload: Data ) async throws -> Void - func get(key:String) async throws -> Data -} - - -public protocol IFactorStorage { - func setFactor(metadataPubKey:String, factorKey: String ) async throws -> Void - func getFactor(metadataPubKey:String) async throws -> String -} - -public struct CoreKitOptions { - public var disableHashFactor : Bool - public var Web3AuthClientId : String - public var network : Web3AuthNetwork -} +#if canImport(tkey) + import tkey +#endif -public struct CoreKitAppState :Codable, Equatable { +public class CoreKitAppState: Codable, Equatable { + public static func == (lhs: CoreKitAppState, rhs: CoreKitAppState) -> Bool { + return lhs.factorKey == rhs.factorKey && lhs.metadataPubKey == rhs.metadataPubKey && lhs.deviceMetadataShareIndex == rhs.deviceMetadataShareIndex && lhs.loginTime == rhs.loginTime + } + public var factorKey: String? = nil public var metadataPubKey: String? = nil - + // share index used for backup share recovery public var deviceMetadataShareIndex: String? = nil - - public var loginTime : Date? = nil - + + public var loginTime: Date? = nil + init(factorKey: String? = nil, metadataPubKey: String? = nil, deviceMetadataShareIndex: String? = nil, loginTime: Date? = nil) { self.factorKey = factorKey self.metadataPubKey = metadataPubKey self.deviceMetadataShareIndex = deviceMetadataShareIndex self.loginTime = loginTime } - + // Method to merge data from another instance of MyStruct - mutating func merge(with other: CoreKitAppState) { + func merge(with other: CoreKitAppState) { + //TODO: Is this supposed to be a potentially partial merge vs a deep copy? + // Update properties based on merging logic - if (other.factorKey != nil) { - self.factorKey = other.factorKey + if other.factorKey != nil { + factorKey = other.factorKey } - if (other.metadataPubKey != nil) { - self.metadataPubKey = other.metadataPubKey + if other.metadataPubKey != nil { + metadataPubKey = other.metadataPubKey } - if (other.deviceMetadataShareIndex != nil) { - self.deviceMetadataShareIndex = other.deviceMetadataShareIndex + if other.deviceMetadataShareIndex != nil { + deviceMetadataShareIndex = other.deviceMetadataShareIndex } - if (other.loginTime != nil) { - self.loginTime = other.loginTime + if other.loginTime != nil { + loginTime = other.loginTime } } } -public struct MpcKeyDetails : Codable { - public let tssPubKey : String +public struct MpcKeyDetails: Codable { + public let tssPubKey: String public let metadataPubKey: String public let requiredFactors: Int32 public let threshold: UInt32 - public let shareDescriptions : String - public let total_shares: UInt32 + public let shareDescriptions: String + public let totalShares: UInt32 public let totalFactors: UInt32? // public let requiredFactors: String } -public struct IdTokenLoginParams { - /** - * Name of the verifier created on Web3Auth Dashboard. In case of Aggregate Verifier, the name of the top level aggregate verifier. - */ - public var verifier: String - - /** - * Unique Identifier for the User. The verifier identifier field set for the verifier/ sub verifier. E.g. "sub" field in your on jwt id token. - */ - public var verifierId: String - - /** - * The idToken received from the Auth Provider. - */ - public var idToken: String - - /** - * Name of the sub verifier in case of aggregate verifier setup. This field should only be provided in case of an aggregate verifier. - */ - public var subVerifier: String? - - /** - * Extra verifier params in case of a WebAuthn verifier type. - */ -// public var extraVerifierParams?: WebAuthnExtraParams; - -// /** -// * Any additional parameter (key value pair) you'd like to pass to the login function. -// */ -// public var additionalParams: [String: Any]? - - /** - * Key to import key into Tss during first time login. - */ -// public var importTssKey?: String - - public var domain: String? - - public func toDictionary () throws -> [String:String]{ - var dict : [String: String] = [:] - // Using Mirror to loop through struct members - let mirror = Mirror(reflecting: self) - for case let (label?, value) in mirror.children { - if let value = value as? String { - dict[label] = value - } - } - return dict - } -} +public struct IdTokenLoginParams: Codable { + /** + * Name of the verifier created on Web3Auth Dashboard. In case of Aggregate Verifier, the name of the top level aggregate verifier. + */ + public var verifier: String + + /** + * Unique Identifier for the User. The verifier identifier field set for the verifier/ sub verifier. E.g. "sub" field in your on jwt id token. + */ + public var verifierId: String + + /** + * The idToken received from the Auth Provider. + */ + public var idToken: String + + /** + * Name of the sub verifier in case of aggregate verifier setup. This field should only be provided in case of an aggregate verifier. + */ + public var subVerifier: String? + + /** + * Extra verifier params in case of a WebAuthn verifier type. + */ + // public var extraVerifierParams?: WebAuthnExtraParams; + + // /** + // * Any additional parameter (key value pair) you'd like to pass to the login function. + // */ + // public var additionalParams: [String: Any]? + + /** + * Key to import key into Tss during first time login. + */ + // public var importTssKey?: String -public enum FactorDescriptionTypeModule { - case HashedShare - case SecurityQuestions - case DeviceShare - case SeedPhrase - case PasswordShare - case SocialShare - case Other - - public func toString () -> String { - switch self { - - case .HashedShare: return "hashedShare" - case .SecurityQuestions: return "tssSecurityQuestions" - case .DeviceShare: return "deviceShare" - case .SeedPhrase: return "seedPhrase" - case .PasswordShare: return "passwordShare" - case .SocialShare: return "socialShare" - case .Other: return "Other" - } - } -} - -public enum TssShareType { - case DEVICE - case RECOVERY - - public func toInt32 () -> Int32 { - switch self { - - case .DEVICE: return 2 - case .RECOVERY: return 3 - } - } - public func toString () -> String { - switch self { - - case .DEVICE: return "2" - case .RECOVERY: return "3" - } - } + public var domain: String? } -public struct enableMFARecoveryFactor { - public var factorKey: String? - public var factorTypeDescription: FactorDescriptionTypeModule - public var additionalMetadata: [String:Any] - - public init(factorKey: String? = nil, factorTypeDescription: FactorDescriptionTypeModule = .Other, additionalMetadata: [String : Any] = [:]) { - self.factorKey = factorKey - self.factorTypeDescription = factorTypeDescription - self.additionalMetadata = additionalMetadata - } -} diff --git a/Sources/mpc-core-kit-swift/Interfaces/IFactorStorage.swift b/Sources/mpc-core-kit-swift/Interfaces/IFactorStorage.swift new file mode 100644 index 0000000..73fb0d3 --- /dev/null +++ b/Sources/mpc-core-kit-swift/Interfaces/IFactorStorage.swift @@ -0,0 +1,6 @@ +import Foundation + +public protocol IFactorStorage { + func setFactor(metadataPubKey: String, factorKey: String) async throws -> Void + func getFactor(metadataPubKey: String) async throws -> String +} diff --git a/Sources/mpc-core-kit-swift/Interfaces/ILocalStorage.swift b/Sources/mpc-core-kit-swift/Interfaces/ILocalStorage.swift new file mode 100644 index 0000000..edaa313 --- /dev/null +++ b/Sources/mpc-core-kit-swift/Interfaces/ILocalStorage.swift @@ -0,0 +1,6 @@ +import Foundation + +public protocol ILocalStorage { + func set(key: String, payload: Data) async throws -> Void + func get(key: String) async throws -> Data +} diff --git a/Sources/mpc-core-kit-swift/MpcCoreKitSwift.swift b/Sources/mpc-core-kit-swift/MpcCoreKitSwift.swift new file mode 100644 index 0000000..862026f --- /dev/null +++ b/Sources/mpc-core-kit-swift/MpcCoreKitSwift.swift @@ -0,0 +1,442 @@ +import Foundation +import CustomAuth +import FetchNodeDetails +import SingleFactorAuth +import TorusUtils + +#if canImport(tkey) + import tkey +#endif + +#if canImport(curveSecp256k1) + import curveSecp256k1 +#endif + +public class MpcCoreKit { + internal var selectedTag: String? + internal var factorKey: String? + + // TODO: Replace [String: Any] with [String: Codable], throughout + internal var userInfo: [String: Any]? + + internal var oauthKey: String? + internal var network: Web3AuthNetwork + internal var option: CoreKitOptions + + internal var appState: CoreKitAppState + + public var metadataHostUrl: String? + + public var tkey: ThresholdKey? + + public var tssEndpoints: [String]? + public var authSigs: [String]? + public var verifier: String? + public var verifierId: String? + + public var torusUtils: TorusUtils + public var nodeIndexes: [Int]? + public var nodeDetails: AllNodeDetailsModel? + + public var nodeDetailsManager: NodeDetailManager + + public var sigs: [String]? + + public var coreKitStorage: CoreKitStorage + + private let storeKey = "corekitStore" + + private let localAppStateKey = "localAppState" + + // init + // TODO: This should accept a param class instead + public init(web3AuthClientId: String, web3AuthNetwork: Web3AuthNetwork, disableHashFactor: Bool = false, localStorage: ILocalStorage, manualSync: Bool = false) throws { + if web3AuthClientId.isEmpty { + throw CoreKitError.invalidInput + } + + option = CoreKitOptions(disableHashFactor: disableHashFactor, Web3AuthClientId: web3AuthClientId, network: web3AuthNetwork, manualSync: manualSync) // TODO: This could be passed in instead + + appState = CoreKitAppState() + + network = web3AuthNetwork + + torusUtils = TorusUtils(enableOneKey: true, network: network.toTorusNetwork(), clientId: web3AuthClientId) + + nodeDetailsManager = NodeDetailManager(network: network.toTorusNetwork()) + + coreKitStorage = CoreKitStorage(storeKey: storeKey, storage: localStorage) + } + + public func updateAppState(state: CoreKitAppState) async throws { + appState.merge(with: state) + + let jsonState = try JSONEncoder().encode(appState).bytes + try await coreKitStorage.set(key: localAppStateKey, payload: jsonState) + } + + public func getCurrentFactorKey() throws -> String { + guard let factor = appState.factorKey else { + throw CoreKitError.factorKeyUnavailable + } + return factor + } + + public func getDeviceMetadataShareIndex() throws -> String { + guard let shareIndex = appState.deviceMetadataShareIndex else { + throw CoreKitError.notFound(msg: "share index not found") + } + return shareIndex + } + + // TODO: This should accept a param class instead + public func loginWithOAuth(loginProvider: LoginProviders, clientId: String, verifier: String, jwtParams: [String: String] = [:], redirectURL: String = "tdsdk://tdsdk/oauthCallback", browserRedirectURL: String = "https://scripts.toruswallet.io/redirect.html") async throws -> MpcKeyDetails { + if loginProvider == .jwt && jwtParams.isEmpty { + throw CoreKitError.notFound(msg: "jwt login should provide jwtParams") + } + + let sub = SubVerifierDetails(loginType: .web, + loginProvider: loginProvider, + clientId: clientId, + verifier: verifier, + redirectURL: redirectURL, + browserRedirectURL: browserRedirectURL, + jwtParams: jwtParams + ) + + let customAuth = CustomAuth(web3AuthClientId: option.Web3AuthClientId, aggregateVerifierType: .singleLogin, aggregateVerifier: verifier, subVerifierDetails: [sub], network: network.toTorusNetwork(), enableOneKey: true) + + let userData = try await customAuth.triggerLogin() + return try await login(userData: userData) + } + + // mneomonic to share + public func mnemonicToKey(shareMnemonic: String, format: String) throws -> String { + // Assuming ShareSerializationModule.deserializeMnemonic returns Data + let factorKey = try ShareSerializationModule.deserialize_share(threshold_key: tkey!, share: shareMnemonic, format: format) + return factorKey + } + + // share to mneomonic + public func keyToMnemonic(factorKey: String, format: String) throws -> String { + // Assuming ShareSerializationModule.deserializeMnemonic returns Data + let mnemonic = try ShareSerializationModule.serialize_share(threshold_key: tkey!, share: factorKey, format: format) + return mnemonic + } + + public func loginWithJwt(verifier: String, verifierId: String, idToken: String, userInfo: [String: Any] = [:]) async throws -> MpcKeyDetails { + let singleFactor = SingleFactorAuth(singleFactorAuthArgs: SingleFactorAuthArgs(web3AuthClientId: option.Web3AuthClientId, network: network)) + + let torusKey = try await singleFactor.getTorusKey(loginParams: LoginParams(verifier: verifier, verifierId: verifierId, idToken: idToken)) + var modUserInfo = userInfo + modUserInfo.updateValue(verifier, forKey: "verifier") + modUserInfo.updateValue(verifierId, forKey: "verifierId") + return try await login(userData: TorusKeyData(torusKey: torusKey, userInfo: modUserInfo)) + } + + public func getUserInfo() throws -> [String: Any] { + guard let userInfo = userInfo else { + throw CoreKitError.notLoggedIn + } + return userInfo + } + + public func getKeyDetails() async throws -> MpcKeyDetails { + if tkey == nil { + throw CoreKitError.notInitialized + } + + guard let finalKeyDetails = try tkey?.get_key_details() else { + throw CoreKitError.keyDetailsNotFound + } + + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let tssTag = try TssModule.get_tss_tag(threshold_key: tkey!) + let tssPubKey = try await TssModule.get_tss_pub_key(threshold_key: tkey!, tss_tag: tssTag) + + let factorsCount = try await getAllFactorPubs().count + let keyDetails = MpcKeyDetails( + tssPubKey: tssPubKey, + metadataPubKey: try finalKeyDetails.pub_key.getPublicKey(format: PublicKeyEncoding.FullAddress), + requiredFactors: finalKeyDetails.required_shares, + threshold: finalKeyDetails.threshold, + shareDescriptions: finalKeyDetails.share_descriptions, + totalShares: finalKeyDetails.total_shares, + totalFactors: UInt32(factorsCount) + 1 + ) + return keyDetails + } + + // login should return key_details + // with factor key if new user + // with required factor > 0 if existing user + private func login(userData: TorusKeyData) async throws -> MpcKeyDetails { + oauthKey = userData.torusKey.oAuthKeyData?.privKey + userInfo = userData.userInfo + + guard let verifierLocal = userData.userInfo["verifier"] as? String, let verifierIdLocal = userData.userInfo["verifierId"] as? String else { + throw CoreKitError.invalidVerifierOrVerifierID + } + + verifier = verifierLocal + verifierId = verifierIdLocal + + // get from service provider/ torusUtils + nodeIndexes = [] + + let fnd = nodeDetailsManager + let nodeDetails = try await fnd.getNodeDetails(verifier: verifierLocal, verifierID: verifierIdLocal) + + guard let host = nodeDetails.getTorusNodeEndpoints().first else { + throw CoreKitError.invalidNode + } + guard let metadatahost = URL(string: host)?.host else { + throw CoreKitError.invalidMetadataEndpoint + } + + let metadataEndpoint = "https://" + metadatahost + "/metadata" + + metadataHostUrl = metadataEndpoint + + self.nodeDetails = nodeDetails + + tssEndpoints = nodeDetails.torusNodeTSSEndpoints + + guard let postboxkey = oauthKey else { + throw CoreKitError.invalidPostboxKey + } + + guard let sessionData = userData.torusKey.sessionData else { + throw CoreKitError.invalidSessionData + } + + let sessionTokenData = sessionData.sessionTokenData + + let signatures = sessionTokenData.map { token in + ["data": Data(hex: token!.token).base64EncodedString(), + "sig": token!.signature] + } + + let sigs: [String] = try signatures.map { String(decoding: try JSONSerialization.data(withJSONObject: $0), as: UTF8.self) } + + authSigs = sigs + + // initialize tkey + let storage_layer = try StorageLayer(enable_logging: true, host_url: metadataEndpoint, server_time_offset: 2) + + let service_provider = try ServiceProvider(enable_logging: true, postbox_key: postboxkey, useTss: true, verifier: verifier, verifierId: verifierId, nodeDetails: nodeDetails) + + let rss_comm = try RssComm() + + tkey = try ThresholdKey( + storage_layer: storage_layer, + service_provider: service_provider, + enable_logging: true, + manual_sync: option.manualSync, + rss_comm: rss_comm) + + let key_details = try await tkey!.initialize(never_initialize_new_key: false, include_local_metadata_transitions: false) + appState.metadataPubKey = try key_details.pub_key.getPublicKey(format: .EllipticCompress) + + if key_details.required_shares > 0 { + try await existingUser() + } else { + try await newUser() + } + + // to add tss pub details to corekit details + let finalKeyDetails = try tkey!.get_key_details() + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let tssTag = try TssModule.get_tss_tag(threshold_key: tkey!) + + let tssPubKey = try await TssModule.get_tss_pub_key(threshold_key: tkey!, tss_tag: tssTag) + + return MpcKeyDetails(tssPubKey: tssPubKey, metadataPubKey: try finalKeyDetails.pub_key.getPublicKey(format: .EllipticCompress), requiredFactors: finalKeyDetails.required_shares, threshold: finalKeyDetails.threshold, shareDescriptions: finalKeyDetails.share_descriptions, totalShares: finalKeyDetails.total_shares, totalFactors: 0) + } + + private func existingUser() async throws { + guard tkey != nil else { + throw CoreKitError.invalidTKey + } + + // try check for hash factor + if option.disableHashFactor == false { + let factor = try Utilities.getHashedPrivateKey(postboxKey: oauthKey!, clientID: option.Web3AuthClientId) + // TODO: factors need to be verified before insertion + try await inputFactor(factorKey: factor) + factorKey = factor + } else { + let factor = try await getDeviceFactor() + // TODO: factors need to be verified before insertion + try await inputFactor(factorKey: factor) + factorKey = factor + } + } + + private func newUser() async throws { + guard let tkey = tkey else { + throw CoreKitError.invalidTKey + } + guard let nodeDetails = nodeDetails else { + throw CoreKitError.nodeDetailsMissing + } + + let _ = try await tkey.reconstruct() + + // TSS Module Initialize - create default tag + // generate factor key or use oauthkey hash as factor + let factorKey: String + let descriptionTypeModule: FactorType + + if option.disableHashFactor == false { + factorKey = try Utilities.getHashedPrivateKey(postboxKey: oauthKey!, clientID: option.Web3AuthClientId) + descriptionTypeModule = FactorType.HashedShare + + } else { + // random generate + factorKey = try curveSecp256k1.SecretKey().serialize() + descriptionTypeModule = FactorType.DeviceShare + } + + // derive factor pub + let factorPub = try curveSecp256k1.SecretKey(hex: factorKey).toPublic().serialize(compressed: false) + + // use input to create tag tss share + let tssIndex = TssShareType.device + + let defaultTag = "default" + try await TssModule.create_tagged_tss_share(threshold_key: tkey, tss_tag: defaultTag, deviceTssShare: nil, factorPub: factorPub, deviceTssIndex: tssIndex.rawValue, nodeDetails: nodeDetails, torusUtils: torusUtils) + + // backup metadata share using factorKey + // finding device share index + var shareIndexes = try tkey.get_shares_indexes() + shareIndexes.removeAll(where: { $0 == "1" }) + + try TssModule.backup_share_with_factor_key(threshold_key: tkey, shareIndex: shareIndexes[0], factorKey: factorKey) + + // record share description + let description = createCoreKitFactorDescription(module: descriptionTypeModule, tssIndex: tssIndex) + let jsonStr = try factorDescriptionToJsonStr(dataObj: description) + try await tkey.add_share_description(key: factorPub, description: jsonStr) + + self.factorKey = factorKey + let deviceMetadataShareIndex = try await TssModule.find_device_share_index(threshold_key: tkey, factor_key: factorKey) + + let metadataPubKey = try tkey.get_key_details().pub_key.getPublicKey(format: .EllipticCompress) + try await updateAppState(state: CoreKitAppState(factorKey: factorKey, metadataPubKey: metadataPubKey, deviceMetadataShareIndex: deviceMetadataShareIndex)) + + // save as device factor if hashfactor is disable + if option.disableHashFactor == true { + try await setDeviceFactor(factorKey: factorKey) + } + } + + public func logout() async throws { + appState = CoreKitAppState() + let jsonState = try JSONEncoder().encode(appState).bytes + try await coreKitStorage.set(key: localAppStateKey, payload: jsonState) + } + + public func inputFactor(factorKey: String) async throws { + guard let threshold_key = tkey else { + throw CoreKitError.invalidTKey + } + // input factor + // TODO: factors need to be verified before insertion + try await threshold_key.input_factor_key(factorKey: factorKey) + + // try using better methods ? + let deviceMetadataShareIndex = try await TssModule.find_device_share_index(threshold_key: threshold_key, factor_key: factorKey) + try await updateAppState(state: CoreKitAppState(deviceMetadataShareIndex: deviceMetadataShareIndex)) + + // setup tkey ( assuming only 2 factor is required) + let _ = try await threshold_key.reconstruct() + + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + let _ = try await TssModule.get_tss_share(threshold_key: threshold_key, tss_tag: selectedTag, factorKey: factorKey) + self.factorKey = factorKey + } + + public func publicKey() async throws -> String { + guard let threshold_key = tkey else { + throw CoreKitError.invalidTKey + } + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + + return try await TssModule.get_tss_pub_key(threshold_key: threshold_key, tss_tag: selectedTag) + } + + public func commitChanges() async throws { + // create a copy syncMetadata + guard let tkey = tkey else { + throw CoreKitError.notInitialized + } + try await tkey.sync_local_metadata_transistions() + } + + // To remove reset account function + public func resetAccount() async throws { + guard let postboxkey = oauthKey else { + throw CoreKitError.notLoggedIn + } + + guard let threshold_key = tkey else { + throw CoreKitError.invalidTKey + } + + guard let _ = metadataHostUrl else { + throw CoreKitError.invalidMetadataUrl + } + + try await threshold_key.storage_layer_set_metadata(private_key: postboxkey, json: "{ \"message\": \"KEY_NOT_FOUND\" }") + + // reset appState + try await resetDeviceFactorStore() + try await coreKitStorage.set(key: localAppStateKey, payload: [:]) + +// try await self.coreKitStorage.set(key: self.localAppStateKey, payload: [:]) + } +} + + +extension MpcCoreKit { + public func getDeviceFactor() async throws -> String { + // getMetadataPublicKey compressed + guard let metadataPubKey = appState.metadataPubKey else { + throw CoreKitError.metadataPubKeyUnavailable + } + + let deviceFactorStorage = DeviceFactorStorage(storage: coreKitStorage) + return try await deviceFactorStorage.getFactor(metadataPubKey: metadataPubKey) + } + + public func setDeviceFactor(factorKey: String) async throws { + guard let metadataPubKey = appState.metadataPubKey else { + throw CoreKitError.metadataPubKeyUnavailable + } + let deviceFactorStorage = DeviceFactorStorage(storage: coreKitStorage) + try await deviceFactorStorage.setFactor(metadataPubKey: metadataPubKey, factorKey: factorKey) + } + + internal func resetDeviceFactorStore() async throws { + guard let metadataPubKey = appState.metadataPubKey else { + throw CoreKitError.metadataPubKeyUnavailable + } + try await coreKitStorage.set(key: metadataPubKey, payload: [:]) + } +} + diff --git a/Sources/mpc-core-kit-swift/Tss.swift b/Sources/mpc-core-kit-swift/Tss.swift new file mode 100644 index 0000000..33895c0 --- /dev/null +++ b/Sources/mpc-core-kit-swift/Tss.swift @@ -0,0 +1,275 @@ +import BigInt +import UIKit +import CustomAuth +import Foundation +import TorusUtils +import tssClientSwift + +#if canImport(tkey) + import tkey +#endif + +#if canImport(curveSecp256k1) + import curveSecp256k1 +#endif + +extension MpcCoreKit { + public func getTssPubKey() async throws -> Data { + guard let threshold_key = tkey else { + throw CoreKitError.invalidTKey + } + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + let result = try await TssModule.get_tss_pub_key(threshold_key: threshold_key, tss_tag: selectedTag) + return Data(hex: result) + } + + /// Signing Data without hashing + public func tssSign(message: Data) async throws -> Data { + guard let authSigs = authSigs else { + throw CoreKitError.invalidAuthSignatures + } + + guard let tkey = tkey else { + throw CoreKitError.invalidTKey + } + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: tkey) + // Create tss Client using helper + + let (client, coeffs) = try await bootstrapTssClient(selected_tag: selectedTag) + + // Wait for sockets to be connected + let connected = try client.checkConnected() + if !connected { + throw TSSClientError("Client not connected") + } + + let precompute = try client.precompute(serverCoeffs: coeffs, signatures: authSigs) + let ready = try client.isReady() + if !ready { + throw TSSClientError("Error, client not ready") + } + + let signingMessage = message.base64EncodedString() + let (s, r, v) = try! client.sign(message: signingMessage, hashOnly: true, original_message: "", precompute: precompute, signatures: authSigs) + + try! client.cleanup(signatures: authSigs) + + return r.magnitude.serialize() + s.magnitude.serialize() + Data([v]) + } + + public func getAllFactorPubs() async throws -> [String] { + guard let threshold_key = tkey else { + throw CoreKitError.invalidTKey + } + + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let currentTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + return try await TssModule.get_all_factor_pub(threshold_key: threshold_key, tss_tag: currentTag) + } + + /// * A BN used for encrypting your Device/ Recovery TSS Key Share. You can generate it using `generateFactorKey()` function or use an existing one. + /// + /// factorKey?: BN; + /// Setting the Description of Share - Security Questions, Device Share, Seed Phrase, Password Share, Social Share, Other. Default is Other. + /// + /// shareDescription?: FactorKeyTypeShareDescription; + /// * Additional metadata information you want to be stored alongside this factor for easy identification. + /// additionalMetadata?: Record; + public func createFactor(tssShareIndex: TssShareType, factorKey: String?, factorDescription: FactorType, additionalMetadata: [String: Any] = [:]) async throws -> String { + // check for index is same as factor key + guard let threshold_key = tkey else { + throw CoreKitError.invalidTKey + } + guard let curFactorKey = self.factorKey else { + throw CoreKitError.invalidFactorKey + } + + let newFactor = try factorKey ?? curveSecp256k1.SecretKey().serialize() + + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + print(selectedTag, "selected tag") + let (tssIndex, _) = try await TssModule.get_tss_share(threshold_key: threshold_key, tss_tag: selectedTag, factorKey: curFactorKey) + // create new factor if different index + if tssIndex == String(tssShareIndex.rawValue) { + try await copyFactor(newFactorKey: newFactor, tssShareIndex: tssShareIndex) + } else { + // copy if same index + try await addNewFactor(newFactorKey: newFactor, tssShareIndex: tssShareIndex) + } + + // backup metadata share using factorKey + let shareIndex = try getDeviceMetadataShareIndex() + try TssModule.backup_share_with_factor_key(threshold_key: threshold_key, shareIndex: shareIndex, factorKey: newFactor) + + // update description + let description = createCoreKitFactorDescription(module: FactorType.HashedShare, tssIndex: tssShareIndex) + let jsonStr = try factorDescriptionToJsonStr(dataObj: description) + let factorPub = try curveSecp256k1.SecretKey(hex: newFactor).toPublic().serialize(compressed: true) + try await threshold_key.add_share_description(key: factorPub, description: jsonStr) + + return newFactor + } + + public func deleteFactor(deleteFactorPub: String, deleteFactorKey: String? = nil) async throws { + guard let threshold_key = tkey, let factorKey = factorKey, let sigs = authSigs else { + throw CoreKitError.invalidTKey + } + + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + + try await TssModule.delete_factor_pub(threshold_key: threshold_key, tss_tag: selectedTag, factor_key: factorKey, auth_signatures: sigs, delete_factor_pub: deleteFactorPub, nodeDetails: nodeDetails!, torusUtils: torusUtils) + + // delete backup metadata share with factorkey + if let deleteFactorKey = deleteFactorKey { + let factorkey = try curveSecp256k1.SecretKey(hex: deleteFactorKey) + if try factorkey.toPublic().serialize(compressed: true) != curveSecp256k1.PublicKey(hex: deleteFactorPub).serialize(compressed: true) { + // unmatch public key + throw CoreKitError.factorKeyAndFactorPubMismatch + } + // set metadata to Not Found + try await tkey?.storage_layer_set_metadata(private_key: deleteFactorKey, json: "{ \"message\": \"KEY_NOT_FOUND\" }") + } + } + + private func copyFactor(newFactorKey: String, tssShareIndex: TssShareType) async throws { + guard let threshold_key = tkey, let factorKey = factorKey else { + throw CoreKitError.invalidTKey + } + + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + + let newkey = try curveSecp256k1.SecretKey(hex: newFactorKey) + let newFactorPub = try newkey.toPublic().serialize(compressed: true) + + // backup metadata share with factorkey + let shareIndex = try getDeviceMetadataShareIndex() + try TssModule.backup_share_with_factor_key(threshold_key: threshold_key, shareIndex: shareIndex, factorKey: newFactorKey) + + try await TssModule.copy_factor_pub(threshold_key: threshold_key, tss_tag: selectedTag, factorKey: factorKey, newFactorPub: newFactorPub, tss_index: tssShareIndex.rawValue) + } + + private func addNewFactor(newFactorKey: String, tssShareIndex: TssShareType) async throws { + guard let threshold_key = tkey, let factorKey = factorKey, let sigs = authSigs else { + throw CoreKitError.invalidTKey + } + + let tssTags = try TssModule.get_all_tss_tags(threshold_key: tkey!) + if tssTags.isEmpty { + throw CoreKitError.noTssTags + } + let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) + + let newkey = try curveSecp256k1.SecretKey(hex: newFactorKey) + let newFactorPub = try newkey.toPublic().serialize(compressed: true) + + // backup metadata share with factorkey + let shareIndex = try getDeviceMetadataShareIndex() + try TssModule.backup_share_with_factor_key(threshold_key: threshold_key, shareIndex: shareIndex, factorKey: newFactorKey) + + try await TssModule.add_factor_pub(threshold_key: threshold_key, tss_tag: selectedTag, factor_key: factorKey, auth_signatures: sigs, new_factor_pub: newFactorPub, new_tss_index: tssShareIndex.rawValue, nodeDetails: nodeDetails!, torusUtils: torusUtils) + } + + public func enableMFA(enableMFA: MFARecoveryFactor = MFARecoveryFactor()) async throws { + if appState.metadataPubKey == nil { + throw CoreKitError.invalidMetadataPubKey + } + + let hashFactorKey = try Utilities.getHashedPrivateKey(postboxKey: oauthKey!, clientID: option.Web3AuthClientId) + let currentFactor = try getCurrentFactorKey() + + if currentFactor != hashFactorKey { + throw CoreKitError.currentFactorNotHashFactor + } + + let additionalDeviceMetadata = await [ + "device": UIDevice.current.model, + "name": UIDevice.current.name, + ] + let deviceFactor = try await createFactor(tssShareIndex: .device, factorKey: nil, factorDescription: .DeviceShare, additionalMetadata: additionalDeviceMetadata) + + // store to device + try await setDeviceFactor(factorKey: deviceFactor) + try await inputFactor(factorKey: deviceFactor) + + // delete hash factor key + let hashFactorPub = try curveSecp256k1.SecretKey(hex: hashFactorKey).toPublic().serialize(compressed: true) + try await deleteFactor(deleteFactorPub: hashFactorPub, deleteFactorKey: hashFactorKey) + } + + public func enableMFAWithRecoveryFactor(enableMFA: MFARecoveryFactor = MFARecoveryFactor()) async throws -> String { + try await self.enableMFA() + let recovery = try await createFactor(tssShareIndex: .recovery, factorKey: enableMFA.factorKey, factorDescription: enableMFA.factorTypeDescription, additionalMetadata: enableMFA.additionalMetadata) + return recovery + } + + private func bootstrapTssClient(selected_tag: String) async throws -> (TSSClient, [String: String]) { + guard let tkey = tkey else { + throw CoreKitError.invalidTKey + } + + guard let verifier = verifier, let verifierId = verifierId, let tssEndpoints = tssEndpoints, let factorKey = factorKey, let nodeIndexes = nodeIndexes else { + throw CoreKitError.invalidInput + } + + let tssNonce = try TssModule.get_tss_nonce(threshold_key: tkey, tss_tag: selected_tag) + + let compressed = try await TssModule.get_tss_pub_key(threshold_key: tkey, tss_tag: selected_tag) + + let publicKey = try curveSecp256k1.PublicKey(hex: compressed).serialize(compressed: false) + + let (tssIndex, tssShare) = try await TssModule.get_tss_share(threshold_key: tkey, tss_tag: selected_tag, factorKey: factorKey) + + if publicKey.count < 128 || publicKey.count > 130 { + throw CoreKitError.requireUncompressedPublicKey + } + + // generate a random nonce for sessionID + let randomKey = try BigUInt(Data(hexString: curveSecp256k1.SecretKey().serialize())!) + let random = BigInt(sign: .plus, magnitude: randomKey) + BigInt(Date().timeIntervalSince1970) + let sessionNonce = TSSHelpers.base64ToBase64url(base64: try TSSHelpers.hashMessage(message: random.magnitude.serialize().addLeading0sForLength64().toHexString())) + + // create the full session string + let session = TSSHelpers.assembleFullSession(verifier: verifier, verifierId: verifierId, tssTag: selected_tag, tssNonce: String(tssNonce), sessionNonce: sessionNonce) + + let userTssIndex = BigInt(tssIndex, radix: 16)! + // total parties, including the client + let parties = nodeIndexes.count > 0 ? nodeIndexes.count + 1 : 4 + + // index of the client, last index of partiesIndexes + let clientIndex = Int32(parties - 1) + + let (urls, socketUrls, partyIndexes, nodeInd) = try TSSHelpers.generateEndpoints(parties: parties, clientIndex: Int(clientIndex), nodeIndexes: nodeIndexes, urls: tssEndpoints) + + let coeffs = try TSSHelpers.getServerCoefficients(participatingServerDKGIndexes: nodeInd.map({ BigInt($0) }), userTssIndex: userTssIndex) + + let shareUnsigned = BigUInt(tssShare, radix: 16)! + let share = try TSSHelpers.denormalizeShare(participatingServerDKGIndexes: nodeInd.map({ BigInt($0) }), userTssIndex: userTssIndex, userTssShare: BigInt(sign: .plus, magnitude: shareUnsigned)) + + let client = try TSSClient(session: session, index: Int32(clientIndex), parties: partyIndexes.map({ Int32($0) }), endpoints: urls.map({ URL(string: $0 ?? "") }), tssSocketEndpoints: socketUrls.map({ URL(string: $0 ?? "") }), share: TSSHelpers.base64Share(share: share), pubKey: try TSSHelpers.base64PublicKey(pubKey: Data(hex: publicKey))) + + return (client, coeffs) + } +} diff --git a/Sources/mpc-core-kit-swift/Utilities/FactorSerialization.swift b/Sources/mpc-core-kit-swift/Utilities/FactorSerialization.swift new file mode 100644 index 0000000..a0f2ffb --- /dev/null +++ b/Sources/mpc-core-kit-swift/Utilities/FactorSerialization.swift @@ -0,0 +1,15 @@ +import Foundation + +#if canImport(tkey) + import tkey +#endif + +class FactorSerialization { + public static func mnemonicToKey(tkey: ThresholdKey, shareMnemonic: String) throws -> String { + return try ShareSerializationModule.deserialize_share(threshold_key: tkey, share: shareMnemonic) + } + + public static func keyToMnemonic(tkey: ThresholdKey, shareHex: String) throws -> String { + return try ShareSerializationModule.serialize_share(threshold_key: tkey, share: shareHex) + } +} diff --git a/Sources/mpc-core-kit-swift/Utilities/Utilities.swift b/Sources/mpc-core-kit-swift/Utilities/Utilities.swift new file mode 100644 index 0000000..0efc8cb --- /dev/null +++ b/Sources/mpc-core-kit-swift/Utilities/Utilities.swift @@ -0,0 +1,55 @@ +import Foundation +import JWTDecode + +#if canImport(curveSecp256k1) + import curveSecp256k1 +#endif + +#if canImport(tssClientSwift) + import tssClientSwift +#endif + +#if canImport(tkey) + import tkey +#endif + +class Utilities { + public static func generateFactorKey() throws -> (SecretKey, PublicKey) { + let factorKey = SecretKey() + let factorPub = try factorKey.toPublic() + return (factorKey, factorPub) + } + + public static func generateTssEndpoints(tssNodeEndpoints: [String], parties: Int, clientIndex: Int, nodeIndexes: [Int?] = []) throws -> ([String?], [String?], partyIndexes: [Int], nodeIndexes: [Int]) { + return try TSSHelpers.generateEndpoints(parties: parties, clientIndex: clientIndex, nodeIndexes: nodeIndexes, urls: tssNodeEndpoints) + } + + public static func parseToken(token: String) throws -> any JWT { + let jwt = try decode(jwt: token) + return jwt + } + + public static func getHashedPrivateKey(postboxKey: String, clientID: String) throws -> String { + if postboxKey.isEmpty || clientID.isEmpty { + throw CoreKitError.invalidInput + } + + let uid = postboxKey + "_" + clientID + + let hashUID = try uid.data(using: .utf8)!.sha3(varient: .KECCAK256).hexString + + let key = try curveSecp256k1.SecretKey(hex: hashUID).serialize() + + return key + } + + public static func convertPublicKeyFormat(publicKey: String, outFormat: PublicKeyEncoding) throws -> String { + let point = try KeyPoint(address: publicKey) + let result = try point.getPublicKey(format: outFormat) + return result + } + + public static func hashMessage(message: String) throws -> String { + return try TSSHelpers.hashMessage(message: message) + } +} diff --git a/Sources/mpc-core-kit-swift/core/ChangeSecurityQuestionParams.swift b/Sources/mpc-core-kit-swift/core/ChangeSecurityQuestionParams.swift new file mode 100644 index 0000000..beed632 --- /dev/null +++ b/Sources/mpc-core-kit-swift/core/ChangeSecurityQuestionParams.swift @@ -0,0 +1,13 @@ +import Foundation + +public class ChangeSecurityQuestionParams: Codable { + public var newQuestion: String + public var newAnswer: String + public var answer: String + + public init(newQuestion: String, newAnswer: String, answer: String) { + self.newQuestion = newQuestion + self.newAnswer = newAnswer + self.answer = answer + } +} diff --git a/Sources/mpc-core-kit-swift/core/CoreKitOptions.swift b/Sources/mpc-core-kit-swift/core/CoreKitOptions.swift new file mode 100644 index 0000000..637e142 --- /dev/null +++ b/Sources/mpc-core-kit-swift/core/CoreKitOptions.swift @@ -0,0 +1,9 @@ +import Foundation +import SingleFactorAuth + +public struct CoreKitOptions { + public var disableHashFactor: Bool + public var Web3AuthClientId: String + public var network: Web3AuthNetwork + public var manualSync: Bool +} diff --git a/Sources/mpc-core-kit-swift/core/FactorType.swift b/Sources/mpc-core-kit-swift/core/FactorType.swift new file mode 100644 index 0000000..6aecc9d --- /dev/null +++ b/Sources/mpc-core-kit-swift/core/FactorType.swift @@ -0,0 +1,11 @@ +import Foundation + +public enum FactorType: String { + case HashedShare = "hashedShare" + case SecurityQuestions = "tssSecurityQuestions" + case DeviceShare = "deviceShare" + case SeedPhrase = "seedPhrase" + case PasswordShare = "passwordShare" + case SocialShare = "socialShare" + case Other = "Other" +} diff --git a/Sources/mpc-core-kit-swift/core/MFARecoveryFactor.swift b/Sources/mpc-core-kit-swift/core/MFARecoveryFactor.swift new file mode 100644 index 0000000..1155e22 --- /dev/null +++ b/Sources/mpc-core-kit-swift/core/MFARecoveryFactor.swift @@ -0,0 +1,13 @@ +import Foundation + +public class MFARecoveryFactor { + public var factorKey: String? + public var factorTypeDescription: FactorType + public var additionalMetadata: [String: Codable] + + public init(factorKey: String? = nil, factorTypeDescription: FactorType = .Other, additionalMetadata: [String: Codable] = [:]) { + self.factorKey = factorKey + self.factorTypeDescription = factorTypeDescription + self.additionalMetadata = additionalMetadata + } +} diff --git a/Sources/mpc-core-kit-swift/core/SecurityQuestionStore.swift b/Sources/mpc-core-kit-swift/core/SecurityQuestionStore.swift new file mode 100644 index 0000000..8b40568 --- /dev/null +++ b/Sources/mpc-core-kit-swift/core/SecurityQuestionStore.swift @@ -0,0 +1,13 @@ +import Foundation + +public class TssSecurityQuestionStore: Codable { + public var shareIndex: String + public var factorPublicKey: String + public var question: String + + public init(shareIndex: String, factorPublicKey: String, question: String) { + self.shareIndex = shareIndex + self.factorPublicKey = factorPublicKey + self.question = question + } +} diff --git a/Sources/mpc-core-kit-swift/core/SetSecurityQuestionParams.swift b/Sources/mpc-core-kit-swift/core/SetSecurityQuestionParams.swift new file mode 100644 index 0000000..b11be55 --- /dev/null +++ b/Sources/mpc-core-kit-swift/core/SetSecurityQuestionParams.swift @@ -0,0 +1,19 @@ +import Foundation + +public class SetSecurityQuestionParams: Codable { + public var question: String + public var answer: String + public var shareType: TssShareType + public var description: String? + public var tssIndex: TssShareType + public var tssTag: String + + public init(question: String, answer: String, shareType: TssShareType = TssShareType.recovery, description: String? = nil, tssIndex: TssShareType, tssTag: String) { + self.question = question + self.answer = answer + self.shareType = shareType + self.description = description + self.tssIndex = tssIndex + self.tssTag = tssTag + } +} diff --git a/Sources/mpc-core-kit-swift/core/TssShareType.swift b/Sources/mpc-core-kit-swift/core/TssShareType.swift new file mode 100644 index 0000000..2bbf612 --- /dev/null +++ b/Sources/mpc-core-kit-swift/core/TssShareType.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum TssShareType: Int32, Codable { + case device = 2 + case recovery = 3 +} diff --git a/Sources/mpc-core-kit-swift/mpcCoreKitSwift.swift b/Sources/mpc-core-kit-swift/mpcCoreKitSwift.swift deleted file mode 100644 index 399a41a..0000000 --- a/Sources/mpc-core-kit-swift/mpcCoreKitSwift.swift +++ /dev/null @@ -1,422 +0,0 @@ -// -// File.swift -// -// -// Created by CW Lee on 10/01/2024. -// - -import Foundation -import tkey - -import CustomAuth -import TorusUtils -import FetchNodeDetails -import SingleFactorAuth - - - -import curveSecp256k1 - -public struct MpcCoreKit { - - internal var selectedTag: String?; - internal var factorKey: String?; - - internal var userInfo: [String: Any]?; - - internal var oauthKey: String?; - internal var network: Web3AuthNetwork; - internal var option: CoreKitOptions; - - internal var appState : CoreKitAppState; - - public var metadataHostUrl : String?; - - public var tkey : ThresholdKey?; - - public var tssEndpoints: [String]?; - public var authSigs: [String]?; - public var verifier: String?; - public var verifierId: String?; - - public var torusUtils: TorusUtils; - public var nodeIndexes: [Int]?; - public var nodeDetails : AllNodeDetailsModel? ; - - public var nodeDetailsManager : NodeDetailManager; - - public var sigs: [String]?; - - public var coreKitStorage : CoreKitStorage - - private let storeKey = "corekitStore" - - private let localAppStateKey = "localAppState" - - - // init - public init( web3AuthClientId : String , web3AuthNetwork: Web3AuthNetwork, disableHashFactor : Bool = false, localStorage: ILocalStorage ) { - self.option = .init(disableHashFactor: disableHashFactor , Web3AuthClientId: web3AuthClientId, network: web3AuthNetwork) - self.appState = CoreKitAppState.init() - - self.network = web3AuthNetwork - - self.torusUtils = TorusUtils( enableOneKey: true, - network: self.network.toTorusNetwork(), clientId: web3AuthClientId ) - - self.nodeDetailsManager = NodeDetailManager(network: self.network.toTorusNetwork()) - - self.coreKitStorage = .init(storeKey: self.storeKey, storage: localStorage) - - } - -// public mutating func rehydrate() async throws { -// let appState : CoreKitAppState = try await self.coreKitStorage.get(key: self.localAppStateKey) - // _ -// } - - public mutating func updateAppState( state: CoreKitAppState) async throws { - // mutating self - self.appState.merge(with: state) - - let jsonState = try JSONEncoder().encode(self.appState).bytes - try await self.coreKitStorage.set(key: self.localAppStateKey, payload: jsonState ) - } - - public func getCurrentFactorKey() throws -> String { - guard let factor = self.appState.factorKey else { - throw "factor key absent" - } - return factor - } - - public func getDeviceMetadataShareIndex() throws -> String { - guard let shareIndex = self.appState.deviceMetadataShareIndex else { - throw "share index not found" - } - return shareIndex - } - - public mutating func loginWithOAuth(loginProvider: LoginProviders, clientId: String, verifier: String , jwtParams: [String: String] = [:], redirectURL: String = "tdsdk://tdsdk/oauthCallback", browserRedirectURL: String = "https://scripts.toruswallet.io/redirect.html" ) async throws -> MpcKeyDetails { - if loginProvider == .jwt && jwtParams.isEmpty { - throw "jwt login should provide jwtParams" - } - - let sub = SubVerifierDetails( loginType: .web, - loginProvider: loginProvider, - clientId: clientId, - verifier: verifier, - redirectURL: redirectURL, - browserRedirectURL: browserRedirectURL, - jwtParams: jwtParams - ) - let customAuth = CustomAuth(web3AuthClientId: option.Web3AuthClientId, aggregateVerifierType: .singleLogin, aggregateVerifier: verifier, subVerifierDetails: [sub], network: self.network.toTorusNetwork(), enableOneKey: true) - - let userData = try await customAuth.triggerLogin() - return try await self.login(userData: userData) - } - - - // mneomonic to share - public func mnemonicToKey(shareMnemonic: String, format: String) -> String? { - // Assuming ShareSerializationModule.deserializeMnemonic returns Data - let factorKey = try? ShareSerializationModule.deserialize_share(threshold_key: tkey!, share: shareMnemonic, format: format); - return factorKey; - } - - // share to mneomonic - public func keyToMnemonic(factorKey: String, format: String) -> String? { - // Assuming ShareSerializationModule.deserializeMnemonic returns Data - let mnemonic = try? ShareSerializationModule.serialize_share(threshold_key: tkey!, share: factorKey, format: format) - return mnemonic - } - public mutating func loginWithJwt(verifier: String, verifierId: String, idToken: String , userInfo : [String:Any] = [:] ) async throws -> MpcKeyDetails { - let singleFactor = SingleFactorAuth(singleFactorAuthArgs: .init( web3AuthClientId: self.option.Web3AuthClientId ,network: self.network)) - - let torusKey = try await singleFactor.getTorusKey(loginParams: .init(verifier: verifier, verifierId: verifierId, idToken: idToken)) - var modUserInfo = userInfo - modUserInfo.updateValue(verifier, forKey: "verifier") - modUserInfo.updateValue(verifierId, forKey: "verifierId") - return try await self.login(userData: TorusKeyData(torusKey: torusKey, userInfo: modUserInfo)) - } - - public func getUserInfo() throws -> [String: Any] { - guard let userInfo = self.userInfo else { - throw ("user is not logged in.") - } - return userInfo - } - - public func getKeyDetails() async throws -> MpcKeyDetails { - if((self.tkey == nil)) { - throw ("Tkey is not initialized!") - } - guard let finalKeyDetails = try self.tkey?.get_key_details() else { - throw ("Key Details Not Found!") - } - let tssTag = try TssModule.get_tss_tag(threshold_key: self.tkey!) - let tssPubKey = try await TssModule.get_tss_pub_key(threshold_key: self.tkey!, tss_tag: tssTag) - - let factorsCount = try await getAllFactorPubs().count - let keyDetails = MpcKeyDetails( - tssPubKey: tssPubKey, - metadataPubKey: try finalKeyDetails.pub_key.getPublicKey(format: PublicKeyEncoding.FullAddress), - requiredFactors: finalKeyDetails.required_shares, - threshold: finalKeyDetails.threshold, - shareDescriptions: finalKeyDetails.share_descriptions, - total_shares: finalKeyDetails.total_shares, - totalFactors: UInt32(factorsCount) + 1 - ) - return keyDetails - } - - // login should return key_details - // with factor key if new user - // with required factor > 0 if existing user - private mutating func login (userData: TorusKeyData) async throws -> MpcKeyDetails { - - self.oauthKey = userData.torusKey.oAuthKeyData?.privKey - self.userInfo = userData.userInfo; - - guard let verifierLocal = userData.userInfo["verifier"] as? String, let verifierIdLocal = userData.userInfo["verifierId"] as? String else { - throw ("Error: invalid verifer, verifierId") - } - - self.verifier = verifierLocal; - self.verifierId = verifierIdLocal; - - // get from service provider/ torusUtils - self.nodeIndexes = [] - - let fnd = self.nodeDetailsManager - let nodeDetails = try await fnd.getNodeDetails(verifier: verifierLocal, verifierID: verifierIdLocal) - - guard let host = nodeDetails.getTorusNodeEndpoints().first else { - throw "Invalid node" - } - guard let metadatahost = URL( string: host)?.host else { - throw "invalid metadata endpoint" - } - - - let metadataEndpoint = "https://" + metadatahost + "/metadata" - - - self.metadataHostUrl = metadataEndpoint - - self.nodeDetails = nodeDetails - - self.tssEndpoints = nodeDetails.torusNodeTSSEndpoints - - guard let postboxkey = self.oauthKey else { - throw RuntimeError("error, invalid postboxkey") - } - - guard let sessionData = userData.torusKey.sessionData else { - throw RuntimeError("error, invalid session data") - } - - let sessionTokenData = sessionData.sessionTokenData - - let signatures = sessionTokenData.map { token in - return [ "data": Data(hex: token!.token).base64EncodedString(), - "sig": token!.signature ] - } - - let sigs: [String] = try signatures.map { String(decoding: try JSONSerialization.data(withJSONObject: $0), as: UTF8.self) } - - self.authSigs = sigs - - // initialize tkey - let storage_layer = try StorageLayer(enable_logging: true, host_url: metadataEndpoint, server_time_offset: 2) - - let service_provider = try ServiceProvider(enable_logging: true, postbox_key: postboxkey, useTss: true, verifier: verifier, verifierId: verifierId, nodeDetails: nodeDetails) - - let rss_comm = try RssComm() - let thresholdKey = try ThresholdKey( - storage_layer: storage_layer, - service_provider: service_provider, - enable_logging: true, - manual_sync: false, - rss_comm: rss_comm) - - let key_details = try await thresholdKey.initialize(never_initialize_new_key: false, include_local_metadata_transitions: false) - self.appState.metadataPubKey = try key_details.pub_key.getPublicKey(format: .EllipticCompress) - - self.tkey = thresholdKey - - if key_details.required_shares > 0 { - try await self.existingUser() - } else { - try await self.newUser() - } - - // to add tss pub details to corekit details - let finalKeyDetails = try thresholdKey.get_key_details() - let tssTag = try TssModule.get_tss_tag(threshold_key: thresholdKey) - let tssPubKey = try? await TssModule.get_tss_pub_key(threshold_key: thresholdKey, tss_tag: tssTag) - return .init(tssPubKey: tssPubKey ?? "", metadataPubKey: try finalKeyDetails.pub_key.getPublicKey(format: .EllipticCompress), requiredFactors: finalKeyDetails.required_shares, threshold: finalKeyDetails.threshold, shareDescriptions: finalKeyDetails.share_descriptions, total_shares: finalKeyDetails.total_shares, totalFactors: 0) - } - - private mutating func existingUser() async throws { - guard let threshold_key = self.tkey else { - throw "Invalid tkey" - } - - var factor: String? - // try check for hash factor - if ( self.option.disableHashFactor == false) { - factor = try? self.getHashKey() - // if factor not found, continue forward and try to retrive device factor - if factor != nil { - do { - try await self.inputFactor(factorKey: factor!) - self.factorKey = factor - return - } catch { - // swallow on invalid hashFactor - } - } - } - - // try check device Storage - do { - factor = try? await self.getDeviceFactor() - // factor not found, return and request factor from inputFactor function - guard let factor = factor else { - print("device Factor not found") - return - } - - try await self.inputFactor(factorKey: factor) - self.factorKey = factor - } catch { - // swallow on invalid device factor - // do not throw to allow input factor - } - } - - private mutating func newUser () async throws { - guard let tkey = self.tkey else { - throw "Invalid tkey" - } - guard let nodeDetails = self.nodeDetails else { - throw "absent nodeDetails" - } - - let _ = try await tkey.reconstruct() - - // TSS Module Initialize - create default tag - // generate factor key or use oauthkey hash as factor - let factorKey : String - let descriptionTypeModule : FactorDescriptionTypeModule - if ( self.option.disableHashFactor == false ) { - factorKey = try self.getHashKey() - descriptionTypeModule = FactorDescriptionTypeModule.HashedShare - - } else { - // random generate - factorKey = try curveSecp256k1.SecretKey().serialize() - descriptionTypeModule = FactorDescriptionTypeModule.DeviceShare - } - - // derive factor pub - let factorPub = try curveSecp256k1.SecretKey(hex: factorKey).toPublic().serialize(compressed: false) - - // use input to create tag tss share - let tssIndex = TssShareType.DEVICE - - let defaultTag = "default" - try await TssModule.create_tagged_tss_share(threshold_key: tkey, tss_tag: defaultTag, deviceTssShare: nil, factorPub: factorPub, deviceTssIndex: tssIndex.toInt32(), nodeDetails: nodeDetails, torusUtils: self.torusUtils) - - // backup metadata share using factorKey - // finding device share index - var shareIndexes = try tkey.get_shares_indexes() - shareIndexes.removeAll(where: {$0 == "1"}) - - try TssModule.backup_share_with_factor_key(threshold_key: tkey, shareIndex: shareIndexes[0], factorKey: factorKey) - - // record share description - let description = createCoreKitFactorDescription(module: descriptionTypeModule, tssIndex: tssIndex) - let jsonStr = try factorDescriptionToJsonStr(dataObj: description) - try await tkey.add_share_description(key: factorPub, description: jsonStr ) - - self.factorKey = factorKey; - let deviceMetadataShareIndex = try await TssModule.find_device_share_index(threshold_key: tkey, factor_key: factorKey) - - let metadataPubKey = try tkey.get_key_details().pub_key.getPublicKey(format: .EllipticCompress) - try await self.updateAppState(state: .init(factorKey: factorKey, metadataPubKey: metadataPubKey, deviceMetadataShareIndex: deviceMetadataShareIndex)) - - // save as device factor if hashfactor is disable - if ( self.option.disableHashFactor == true ) { - try await self.setDeviceFactor(factorKey: factorKey) - } - } - - public mutating func logout () async throws { - self.appState = .init() - let jsonState = try JSONEncoder().encode(self.appState).bytes - try await self.coreKitStorage.set(key: self.localAppStateKey, payload: jsonState) - } - - public mutating func inputFactor (factorKey: String) async throws { - guard let threshold_key = self.tkey else { - throw "Invalid tkey" - } - // input factor - try await threshold_key.input_factor_key(factorKey: factorKey) - - // try using better methods ? - let deviceMetadataShareIndex = try await TssModule.find_device_share_index(threshold_key: threshold_key, factor_key: factorKey) - try await self.updateAppState(state: .init(deviceMetadataShareIndex: deviceMetadataShareIndex)) - - // setup tkey ( assuming only 2 factor is required) - let _ = try await threshold_key.reconstruct() - - let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - let _ = try await TssModule.get_tss_share(threshold_key: threshold_key, tss_tag: selectedTag, factorKey: factorKey) - self.factorKey = factorKey - } - - public func publicKey() async throws -> String { - guard let threshold_key = self.tkey else { - throw "Invalid tkey" - } - let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - - return try await TssModule.get_tss_pub_key(threshold_key: threshold_key, tss_tag: selectedTag) - } - - // To remove reset account function - public func resetAccount () async throws { - guard let postboxkey = self.oauthKey else { - throw "Not yet login via oauth" - } - - guard let threshold_key = self.tkey else { - throw "invalid Tkey" - } - - guard let _ = self.metadataHostUrl else { - throw "invalid metadata url" - } - - try await threshold_key.storage_layer_set_metadata(private_key: postboxkey, json: "{ \"message\": \"KEY_NOT_FOUND\" }") - - // reset appState - try await self.resetDeviceFactorStore() - try await self.coreKitStorage.set(key: self.localAppStateKey, payload: [:]) -// try await self.coreKitStorage.set(key: self.localAppStateKey, payload: [:]) - } - - internal func getHashKey () throws -> String { - guard let oauthKey = self.oauthKey else { - throw "invalid oauth key" - } - guard let uid = try "\(oauthKey)_\(self.option.Web3AuthClientId)".data(using: .utf8)?.sha3(varient: Variants.KECCAK256 ).toHexString() else { - throw "invalid string in getHashKey" - } - let key = try curveSecp256k1.SecretKey(hex: uid).serialize() - return key - } -} diff --git a/Sources/mpc-core-kit-swift/tss.swift b/Sources/mpc-core-kit-swift/tss.swift deleted file mode 100644 index 061b29e..0000000 --- a/Sources/mpc-core-kit-swift/tss.swift +++ /dev/null @@ -1,356 +0,0 @@ -// -// File.swift -// -// -// Created by CW Lee on 19/01/2024. -// - -import Foundation -import CustomAuth -import TorusUtils -import tssClientSwift -import tkey -import curveSecp256k1 -import BigInt -import UIKit - -extension MpcCoreKit { - - public func getTssPubKey () async throws -> Data { - guard let threshold_key = self.tkey else { - throw "Invalid tkey" - } - let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - let result = try await TssModule.get_tss_pub_key(threshold_key: threshold_key, tss_tag: selectedTag) - return Data(hex: result) - } - - - public func getTssPubKey () -> Data { - - let semaphore = DispatchSemaphore(value: 0) - var result : Data? - performAsyncOperation(completion: { myresult in - result = myresult - semaphore.signal() - }) - semaphore.wait() - return result ?? Data([]) - } - - - func performAsyncOperation(completion: @escaping (Data) -> Void) { - Task { - // Simulate an asynchronous operation - let result = try await self.getTssPubKey() - print (result) - completion(result) - } - } - - /// Signing Data without hashing - public func tssSign(message: Data) async throws -> Data { - guard let authSigs = self.authSigs else { - throw TSSClientError("Invalid authSigns") - } - - guard let tkey = self.tkey else { - throw TSSClientError("invalid tkey") - } - - let selectedTag = try TssModule.get_tss_tag(threshold_key: tkey) - // Create tss Client using helper - - let (client, coeffs) = try await self.bootstrapTssClient( selected_tag: selectedTag) - - - // Wait for sockets to be connected - let connected = try client.checkConnected() - if !(connected) { - throw "Client not connected" - } - - let precompute = try client.precompute(serverCoeffs: coeffs, signatures: authSigs) - let ready = try client.isReady() - if !(ready) { - throw RuntimeError("Error, client not ready") - } - - let signingMessage = message.base64EncodedString() - let (s, r, v) = try! client.sign(message: signingMessage, hashOnly: true, original_message: "", precompute: precompute, signatures: authSigs) - - try! client.cleanup(signatures: authSigs) - - return r.magnitude.serialize() + s.magnitude.serialize() + Data([v]) - } - - public func tssSign (message: Data) throws -> Data { - - let semaphore = DispatchSemaphore(value: 0) - var result : Data? - performAsyncTssSignOperation(message: message, completion: { myresult in - result = myresult - semaphore.signal() - }) - let _ = semaphore.wait(timeout: .now() + 100_000_000_000) - - //gepoubkey - guard let signature = result else { - throw RuntimeError("Failed to sign the message") - } - - return signature - } - - func performAsyncTssSignOperation(message:Data, completion: @escaping (Data) -> Void) { - Task { - do { - // Simulate an asynchronous operation - let result = try await self.tssSign(message: message ) - completion(result) - }catch { - completion(Data()) - } - } - } - - public func getAllFactorPubs () async throws -> [String] { - guard let threshold_key = self.tkey else { - throw "tkey is not available" - } - - let currentTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - return try await TssModule.get_all_factor_pub(threshold_key: threshold_key, tss_tag: currentTag) - } - - - /// * A BN used for encrypting your Device/ Recovery TSS Key Share. You can generate it using `generateFactorKey()` function or use an existing one. - /// - /// factorKey?: BN; - /// Setting the Description of Share - Security Questions, Device Share, Seed Phrase, Password Share, Social Share, Other. Default is Other. - /// - /// shareDescription?: FactorKeyTypeShareDescription; - /// * Additional metadata information you want to be stored alongside this factor for easy identification. - /// additionalMetadata?: Record; - public func createFactor( tssShareIndex: TssShareType, factorKey: String?, factorDescription: FactorDescriptionTypeModule, additionalMetadata: [String: Any] = [:]) async throws -> String { - // check for index is same as factor key - guard let threshold_key = self.tkey else { - throw "Invalid tkey" - } - guard let curFactorKey = self.factorKey else { - throw "invalid current FactorKey" - } - - let newFactor = try factorKey ?? curveSecp256k1.SecretKey().serialize() - - let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - print(selectedTag,"selected tag") - let (tssIndex, _ ) = try await TssModule.get_tss_share(threshold_key: threshold_key, tss_tag: selectedTag, factorKey: curFactorKey) - // create new factor if different index - if ( tssIndex == tssShareIndex.toString()) { - try await self.copyFactor(newFactorKey: newFactor, tssShareIndex: tssShareIndex) - } else { - // copy if same index - try await self.addNewFactor(newFactorKey: newFactor, tssShareIndex: tssShareIndex) - } - - // backup metadata share using factorKey - let shareIndex = try self.getDeviceMetadataShareIndex() - try TssModule.backup_share_with_factor_key(threshold_key: threshold_key, shareIndex: shareIndex, factorKey: newFactor) - - // update description - let description = createCoreKitFactorDescription(module: FactorDescriptionTypeModule.HashedShare, tssIndex: tssShareIndex) - let jsonStr = try factorDescriptionToJsonStr(dataObj: description) - let factorPub = try curveSecp256k1.SecretKey(hex: newFactor).toPublic().serialize(compressed: true) - try await threshold_key.add_share_description(key: factorPub, description: jsonStr ) - - return newFactor - } - - public func deleteFactor ( deleteFactorPub: String, deleteFactorKey: String? = nil) async throws { - guard let threshold_key = self.tkey, let factorKey = self.factorKey, let sigs = self.authSigs else { - throw "Invalid tkey" - } - let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - - - try await TssModule.delete_factor_pub(threshold_key: threshold_key, tss_tag: selectedTag, factor_key: factorKey, auth_signatures: sigs, delete_factor_pub: deleteFactorPub, nodeDetails: nodeDetails!, torusUtils: torusUtils) - - // delete backup metadata share with factorkey - if let deleteFactorKey = deleteFactorKey { - let factorkey = try curveSecp256k1.SecretKey(hex: deleteFactorKey) - if try factorkey.toPublic().serialize(compressed: true) != curveSecp256k1.PublicKey(hex: deleteFactorPub).serialize(compressed: true) { - // unmatch public key - throw "unmatch factorPub and factor key" - } - // set metadata to Not Found - try await self.tkey?.storage_layer_set_metadata(private_key: deleteFactorKey, json: "{ \"message\": \"KEY_NOT_FOUND\" }") - } - } - - private func copyFactor ( newFactorKey: String, tssShareIndex: TssShareType ) async throws { - guard let threshold_key = self.tkey, let factorKey = self.factorKey else { - throw "Invalid tkey" - } - let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - - let newkey = try curveSecp256k1.SecretKey(hex: newFactorKey) - let newFactorPub = try newkey.toPublic().serialize(compressed: true) - - // backup metadata share with factorkey - let shareIndex = try self.getDeviceMetadataShareIndex() - try TssModule.backup_share_with_factor_key(threshold_key: threshold_key, shareIndex: shareIndex, factorKey: newFactorKey) - - try await TssModule.copy_factor_pub(threshold_key: threshold_key, tss_tag: selectedTag, factorKey: factorKey, newFactorPub: newFactorPub, tss_index: tssShareIndex.toInt32()) - } - - private func addNewFactor ( newFactorKey: String, tssShareIndex: TssShareType ) async throws { - guard let threshold_key = self.tkey, let factorKey = self.factorKey, let sigs = self.authSigs else { - throw "Invalid tkey" - } - let selectedTag = try TssModule.get_tss_tag(threshold_key: threshold_key) - - let newkey = try curveSecp256k1.SecretKey(hex: newFactorKey) - let newFactorPub = try newkey.toPublic().serialize(compressed: true) - - // backup metadata share with factorkey - let shareIndex = try self.getDeviceMetadataShareIndex() - try TssModule.backup_share_with_factor_key(threshold_key: threshold_key, shareIndex: shareIndex, factorKey: newFactorKey) - - try await TssModule.add_factor_pub(threshold_key: threshold_key, tss_tag: selectedTag, factor_key: factorKey, auth_signatures: sigs, new_factor_pub: newFactorPub, new_tss_index: tssShareIndex.toInt32(), nodeDetails: nodeDetails!, torusUtils: torusUtils) - } - - public mutating func enableMFA ( enableMFA : enableMFARecoveryFactor = .init(), recoveryFactor : Bool = true ) async throws -> String? { -// self.checkHashFactor() - guard let metadataPubKey = self.appState.metadataPubKey else { - throw "invalid metadataPubKey" - } - - let hashFactorKey = try self.getHashKey() - - let additionalDeviceMetadata = await [ - "device" : UIDevice.current.model, - "name" : UIDevice.current.name - ] - let deviceFactor = try await self.createFactor(tssShareIndex: .DEVICE, factorKey: nil, factorDescription: .DeviceShare, additionalMetadata: additionalDeviceMetadata) - - // store to device - try await self.setDeviceFactor(factorKey: deviceFactor) - try await self.inputFactor(factorKey: deviceFactor) - - - // delete hash factor key - let hashFactorPub = try curveSecp256k1.SecretKey(hex: hashFactorKey).toPublic().serialize(compressed: true) - try await self.deleteFactor(deleteFactorPub: hashFactorPub, deleteFactorKey: hashFactorKey) - - if recoveryFactor { - let recovery = try await self.createFactor(tssShareIndex: .RECOVERY, factorKey: enableMFA.factorKey, factorDescription: enableMFA.factorTypeDescription, additionalMetadata: enableMFA.additionalMetadata) - return recovery - } - return nil - } - -// public async enableMFA(enableMFAParams: EnableMFAParams, recoveryFactor = true): Promise { -// this.checkReady(); -// -// const hashedFactorKey = getHashedPrivateKey(this.state.oAuthKey, this.options.hashedFactorNonce); -// if (!(await this.checkIfFactorKeyValid(hashedFactorKey))) { -// if (this.tKey._localMetadataTransitions[0].length) throw new Error("CommitChanges are required before enabling MFA"); -// throw new Error("MFA already enabled"); -// } -// -// try { -// let browserData; -// -// if (this.isNodejsOrRN(this.options.uxMode)) { -// browserData = { -// browserName: "Node Env", -// browserVersion: "", -// deviceName: "nodejs", -// }; -// } else { -// // try { -// const browserInfo = bowser.parse(navigator.userAgent); -// const browserName = `${browserInfo.browser.name}`; -// browserData = { -// browserName, -// browserVersion: browserInfo.browser.version, -// deviceName: browserInfo.os.name, -// }; -// } -// const deviceFactorKey = new BN(await this.createFactor({ shareType: TssShareType.DEVICE, additionalMetadata: browserData }), "hex"); -// if (this.currentStorage instanceof AsyncStorage) { -// asyncStoreFactor(deviceFactorKey, this, this.options.asyncStorageKey); -// } else { -// storeWebBrowserFactor(deviceFactorKey, this, this.options.storageKey); -// } -// await this.inputFactorKey(new BN(deviceFactorKey, "hex")); -// -// const hashedFactorPub = getPubKeyPoint(hashedFactorKey); -// await this.deleteFactor(hashedFactorPub, hashedFactorKey); -// await this.deleteMetadataShareBackup(hashedFactorKey); -// -// // only recovery factor = true -// if (recoveryFactor) { -// const backupFactorKey = await this.createFactor({ shareType: TssShareType.RECOVERY, ...enableMFAParams }); -// return backupFactorKey; -// } -// // update to undefined for next major release -// return ""; -// } catch (err: unknown) { -// log.error("error enabling MFA", err); -// throw new Error((err as Error).message); -// } -// } - - private func bootstrapTssClient (selected_tag: String ) async throws -> (TSSClient, [String: String]) { - - guard let tkey = self.tkey else { - throw TSSClientError("invalid tkey") - } - - guard let verifier = self.verifier, let verifierId = self.verifierId , let tssEndpoints = self.tssEndpoints, let factorKey = self.factorKey, let nodeIndexes = self.nodeIndexes else { - throw TSSClientError("Invalid parameter for tss client") - } - - let tssNonce = try TssModule.get_tss_nonce(threshold_key: tkey, tss_tag: selected_tag) - - let compressed = try await TssModule.get_tss_pub_key(threshold_key: tkey, tss_tag: selected_tag) - - let publicKey = try curveSecp256k1.PublicKey(hex: compressed).serialize(compressed: false) - - let (tssIndex, tssShare) = try await TssModule.get_tss_share(threshold_key: tkey, tss_tag: selected_tag, factorKey: factorKey) - - if ( publicKey.count < 128 || publicKey.count > 130 ) { - throw TSSClientError("Public Key should be in uncompressed format") - } - - // generate a random nonce for sessionID - let randomKey = try BigUInt( Data(hexString: curveSecp256k1.SecretKey().serialize() )! ) - let random = BigInt(sign: .plus, magnitude: randomKey) + BigInt(Date().timeIntervalSince1970) - let sessionNonce = TSSHelpers.base64ToBase64url( base64: try TSSHelpers.hashMessage(message: random.magnitude.serialize().addLeading0sForLength64().toHexString())) - - // create the full session string - let session = TSSHelpers.assembleFullSession(verifier: verifier, verifierId: verifierId, tssTag: selected_tag, tssNonce: String(tssNonce), sessionNonce: sessionNonce) - - let userTssIndex = BigInt(tssIndex, radix: 16)! - // total parties, including the client - let parties = nodeIndexes.count > 0 ? nodeIndexes.count + 1 : 4 - - // index of the client, last index of partiesIndexes - let clientIndex = Int32(parties - 1) - - let (urls, socketUrls, partyIndexes, nodeInd) = try TSSHelpers.generateEndpoints(parties: parties, clientIndex: Int(clientIndex), nodeIndexes: nodeIndexes, urls: tssEndpoints) - - let coeffs = try TSSHelpers.getServerCoefficients(participatingServerDKGIndexes: nodeInd.map({ BigInt($0) }), userTssIndex: userTssIndex) - - let shareUnsigned = BigUInt(tssShare, radix: 16)! - let share = try TSSHelpers.denormalizeShare(participatingServerDKGIndexes: nodeInd.map({ BigInt($0) }), userTssIndex: userTssIndex, userTssShare: BigInt(sign: .plus, magnitude: shareUnsigned)) - - - - let client = try TSSClient(session: session, index: Int32(clientIndex), parties: partyIndexes.map({Int32($0)}), endpoints: urls.map({ URL(string: $0 ?? "") }), tssSocketEndpoints: socketUrls.map({ URL(string: $0 ?? "") }), share: TSSHelpers.base64Share(share: share), pubKey: try TSSHelpers.base64PublicKey(pubKey: Data(hex: publicKey))) - - return (client, coeffs) - } -} diff --git a/Tests/mpc-kit-swiftTests/Helpers/MemoryStorage.swift b/Tests/mpc-kit-swiftTests/Helpers/MemoryStorage.swift new file mode 100644 index 0000000..f8f9b1e --- /dev/null +++ b/Tests/mpc-kit-swiftTests/Helpers/MemoryStorage.swift @@ -0,0 +1,19 @@ +import Foundation +import mpc_core_kit_swift + +internal class MemoryStorage: ILocalStorage { + var memory: [String: Data] = [:] + + public func get(key: String) async throws -> Data { + guard let result = memory[key] else { + throw CoreKitError.notFound(msg: "No value for " + key) + // return Data() + } + return result + } + + public func set(key: String, payload: Data) async throws { + memory.updateValue(payload, forKey: key) + } +} + diff --git a/Tests/mpc-kit-swiftTests/helper_swifttest.swift b/Tests/mpc-kit-swiftTests/helper_swifttest.swift index 0b7ecce..e8d8b33 100644 --- a/Tests/mpc-kit-swiftTests/helper_swifttest.swift +++ b/Tests/mpc-kit-swiftTests/helper_swifttest.swift @@ -1,20 +1,13 @@ -// -// File.swift -// -// -// Created by CW Lee on 19/02/2024. -// - import Foundation -import XCTest @testable import mpc_core_kit_swift +import XCTest -final class helper_swiftTests: XCTestCase { - func testJsonSerialization () throws { - let state = CoreKitAppState.init(factorKey: nil, metadataPubKey: nil ,deviceMetadataShareIndex: "", loginTime: nil ) +class helper_swiftTests: XCTestCase { + func testJsonSerialization() throws { + let state = CoreKitAppState(factorKey: nil, metadataPubKey: nil, deviceMetadataShareIndex: "", loginTime: nil) let jsonState = try JSONEncoder().encode(state).bytes - let result = try JSONSerialization.data(withJSONObject: [ "test":jsonState]) + let result = try JSONSerialization.data(withJSONObject: ["test": jsonState]) let resultObject = try JSONSerialization.jsonObject(with: result) as! [String: Any] let obj = resultObject["test"] as! Array diff --git a/Tests/mpc-kit-swiftTests/mpc_kit_swiftTests.swift b/Tests/mpc-kit-swiftTests/mpc_kit_swiftTests.swift index aa8a301..a2aafc1 100644 --- a/Tests/mpc-kit-swiftTests/mpc_kit_swiftTests.swift +++ b/Tests/mpc-kit-swiftTests/mpc_kit_swiftTests.swift @@ -1,8 +1,8 @@ -import XCTest -@testable import mpc_core_kit_swift -import JWTKit import curveSecp256k1 +import JWTKit +@testable import mpc_core_kit_swift import SingleFactorAuth +import XCTest // JWT payload structure. struct TestPayload: JWTPayload, Equatable { @@ -32,7 +32,7 @@ struct TestPayload: JWTPayload, Equatable { } } -func mockLogin( email: String) async throws -> Data { +func mockLogin(email: String) async throws -> Data { // Create URL let url = URL(string: "https://li6lnimoyrwgn2iuqtgdwlrwvq0upwtr.lambda-url.eu-west-1.on.aws/")! @@ -42,149 +42,124 @@ func mockLogin( email: String) async throws -> Data { request.setValue("application/json", forHTTPHeaderField: "Content-Type") // Create JSON data to send in the request body -//verifier: "torus-key-test", scope: "email", extraPayload: { email }, alg: "ES256" + // verifier: "torus-key-test", scope: "email", extraPayload: { email }, alg: "ES256" let jsonObject: [String: Any] = [ "verifier": "torus-test-health", "scope": email, - "extraPayload" : [ - "email" : email + "extraPayload": [ + "email": email, ], - "alg" : "ES256" + "alg": "ES256", ] let jsonData = try JSONSerialization.data(withJSONObject: jsonObject) request.httpBody = jsonData - + // Perform the request asynchronously let (data, _) = try await URLSession.shared.data(for: request) return data } -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 - } - +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 + } } - final class mpc_kit_swiftTests: XCTestCase { - func resetMPC(email: String, verifier: String, clientId: String) async throws { - var coreKitInstance = MpcCoreKit( web3AuthClientId: clientId, web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: MemoryStorage() ) - let data = try mockLogin2(email: email) + let coreKitInstance = try MpcCoreKit(web3AuthClientId: clientId, web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: MemoryStorage()) + let data = try mockLogin2(email: email) let token = data - - - let keyDetails = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) + + let _ = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) try await coreKitInstance.resetAccount() } - - func testExample() async throws { - // XCTest Documentation - // https://developer.apple.com/documentation/xctest - - // Defining Test Cases and Test Methods - // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods - - let email = "testiosEmail004" + + func testLoginFromWebAccount() async throws { + let email = "testios002" let verifier = "torus-test-health" let clientId = "torus-test-health" - try await resetMPC(email: email, verifier: verifier, clientId: clientId) - +// try await resetMPC(email: email, verifier: verifier, clientId: clientId) + let memoryStorage = MemoryStorage() - var coreKitInstance = MpcCoreKit( web3AuthClientId: clientId, web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: memoryStorage) - - let data = try mockLogin2(email: email) + let coreKitInstance = try MpcCoreKit(web3AuthClientId: clientId, web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: memoryStorage, manualSync: true) + + let data = try mockLogin2(email: email) let token = data -// let dataObj = try JSONSerialization.jsonObject(with: data) as! [String: String] - -// let token = dataObj["token"]! - -// if let jsonString = String(data: data, encoding: .utf8) { -// print("Response: \(jsonString)") -// // Parse JSON response data here using JSONDecoder or other methods -// } -// let jwtParams : IdTokenLoginParams = .init(verifier: verifier, verifierId: email, idToken: token, domain: "com.ios.mpc" ) -// let keyDetails = try await coreKitInstance.login(loginProvider: .jwt, verifier: "test", jwtParams: jwtParams.toDictionary()) - - - let keyDetails = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) - + + let _ = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) + let hash = try keccak256(data: Data(hex: "010203040506")) let signatures = try await coreKitInstance.tssSign(message: hash) print(signatures) // - let newFactor = try await coreKitInstance.createFactor(tssShareIndex: .DEVICE, factorKey: nil, factorDescription: .DeviceShare , additionalMetadata: ["my":"mymy"]) - + let newFactor = try await coreKitInstance.createFactor(tssShareIndex: .device, factorKey: nil, factorDescription: .DeviceShare, additionalMetadata: ["my": "mymy"]) + let deleteFactorPub = try curveSecp256k1.SecretKey(hex: newFactor).toPublic().serialize(compressed: true) try await coreKitInstance.deleteFactor(deleteFactorPub: deleteFactorPub, deleteFactorKey: newFactor) - print (keyDetails) - } - - func testRecoveryFactor() async throws { - + + func testMFARecoveryFactor() async throws { let email = "testiosEmail11mfa" let verifier = "torus-test-health" let clientId = "torus-test-health" + // reset Account try await resetMPC(email: email, verifier: verifier, clientId: clientId) let memoryStorage = MemoryStorage() - var coreKitInstance = MpcCoreKit( web3AuthClientId: clientId, web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: memoryStorage) - let data = try mockLogin2(email: email) + let coreKitInstance = try MpcCoreKit(web3AuthClientId: clientId, web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: memoryStorage) + let data = try mockLogin2(email: email) let token = data - - - let keyDetails = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) - let hash = try Data(hex: "010203040506").sha3(varient:Variants.KECCAK256) - guard let recoveryFactor = try await coreKitInstance.enableMFA() else { throw "empty factor" }; + _ = try await coreKitInstance.loginWithJwt(verifier: verifier, verifierId: email, idToken: token) + + let recoveryFactor = try await coreKitInstance.enableMFAWithRecoveryFactor() let memoryStorage2 = MemoryStorage() - var coreKitInstance2 = MpcCoreKit( web3AuthClientId: "torus-test-health", web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: memoryStorage2); - let data2 = try mockLogin2(email: email) + let coreKitInstance2 = try MpcCoreKit(web3AuthClientId: "torus-test-health", web3AuthNetwork: Web3AuthNetwork.SAPPHIRE_DEVNET, disableHashFactor: false, localStorage: memoryStorage2) + let data2 = try mockLogin2(email: email) let token2 = data2 - - + let keyDetails2 = try await coreKitInstance2.loginWithJwt(verifier: verifier, verifierId: email, idToken: token2) - + + XCTAssertEqual(keyDetails2.requiredFactors, 1) + try await coreKitInstance2.inputFactor(factorKey: recoveryFactor) - let result = try await coreKitInstance.createFactor(tssShareIndex: .DEVICE, factorKey: nil, factorDescription: .DeviceShare) - + _ = try await coreKitInstance.createFactor(tssShareIndex: .device, factorKey: nil, factorDescription: .DeviceShare) + let getKeyDetails = try await coreKitInstance2.getKeyDetails() - XCTAssertEqual(getKeyDetails.requiredFactors, 0); + XCTAssertEqual(getKeyDetails.requiredFactors, 0) - let userInfo = try coreKitInstance2.getUserInfo(); + let userInfo = try coreKitInstance2.getUserInfo() if let verifierId = userInfo["verifierId"] as? String { - XCTAssertEqual(verifierId, email); + XCTAssertEqual(verifierId, email) } else { XCTFail("Verifier ID not matching.") } - - let hash2 = try Data(hex: "010203040506").sha3(varient: Variants.KECCAK256) - let signatures2 = try await coreKitInstance2.tssSign(message: hash2) + + let hash2 = try Data(hex: "010203040506").sha3(varient: Variants.KECCAK256) + _ = try await coreKitInstance2.tssSign(message: hash2) } }