diff --git a/Sources/ThresholdKey/Encryption.swift b/Sources/ThresholdKey/Encryption.swift new file mode 100644 index 0000000..781372e --- /dev/null +++ b/Sources/ThresholdKey/Encryption.swift @@ -0,0 +1,58 @@ +// +// File.swift +// +// +// Created by CW Lee on 12/09/2023. +// + +import Foundation +#if canImport(lib) + import lib +#endif + +// secp256k1 curve +public let secpN = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" + + +/// Encrypts a message. +/// +/// - Returns: `String` +/// +/// - Throws: `RuntimeError`, indicates invalid parameters or invalid `ThresholdKey`. +public func encrypt(key: String, msg: String, curveN: String = secpN ) throws -> String { + var errorCode: Int32 = -1 + let curvePointer = UnsafeMutablePointer(mutating: (curveN as NSString).utf8String) + let keyPointer = UnsafeMutablePointer(mutating: (key as NSString).utf8String) + let msgPointer = UnsafeMutablePointer(mutating: (msg as NSString).utf8String) + + let result = withUnsafeMutablePointer(to: &errorCode, { error in + tkey_encrypt(keyPointer, msgPointer, curvePointer, error) + }) + guard errorCode == 0 else { + throw RuntimeError("Error in encrypt \(errorCode)") + } + let string = String(cString: result!) + string_free(result) + return string +} + +/// Decrypts a message. +/// +/// - Returns: `String` +/// +/// - Throws: `RuntimeError`, indicates invalid parameters or invalid `ThresholdKey`. +public func decrypt(key: String, msg: String) throws -> String { + var errorCode: Int32 = -1 + let keyPointer = UnsafeMutablePointer(mutating: (key as NSString).utf8String) + let msgPointer = UnsafeMutablePointer(mutating: (msg as NSString).utf8String) + + let result = withUnsafeMutablePointer(to: &errorCode, { error in + tkey_decrypt(keyPointer, msgPointer, error) + }) + guard errorCode == 0 else { + throw RuntimeError("Error in decrypt \(errorCode)") + } + let string = String(cString: result!) + string_free(result) + return string +} diff --git a/Sources/ThresholdKey/Modules/TssModule.swift b/Sources/ThresholdKey/Modules/TssModule.swift index f0977e5..d08d823 100644 --- a/Sources/ThresholdKey/Modules/TssModule.swift +++ b/Sources/ThresholdKey/Modules/TssModule.swift @@ -252,9 +252,9 @@ public final class TssModule { /// - torusUtils: torusUtils used to retrieve dkg tss pub key /// /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key. - public static func create_tagged_tss_share(threshold_key: ThresholdKey, tss_tag: String, deviceTssShare: String?, factorPub: String, deviceTssIndex: Int32, nodeDetails: AllNodeDetailsModel, torusUtils: TorusUtils) async throws { + public static func create_tagged_tss_share(threshold_key: ThresholdKey, tss_tag: String, deviceTssShare: String? = nil, factorPub: String, deviceTssIndex: Int32) async throws { try await TssModule.set_tss_tag(threshold_key: threshold_key, tss_tag: tss_tag) - try await TssModule.update_tss_pub_key(threshold_key: threshold_key, tss_tag: tss_tag, nodeDetails: nodeDetails, torusUtils: torusUtils) + try await TssModule.update_tss_pub_key(threshold_key: threshold_key, tss_tag: tss_tag) return try await withCheckedThrowingContinuation { continuation in create_tagged_tss_share(threshold_key: threshold_key, deviceTssShare: deviceTssShare, factorPub: factorPub, deviceTssIndex: deviceTssIndex) { @@ -289,12 +289,12 @@ public final class TssModule { /// - prefetch: Fetch the next nonce's pub key /// /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key. - public static func update_tss_pub_key(threshold_key: ThresholdKey, tss_tag: String, nodeDetails: AllNodeDetailsModel, torusUtils: TorusUtils, prefetch: Bool = false) async throws { + public static func update_tss_pub_key(threshold_key: ThresholdKey, tss_tag: String, prefetch: Bool = false) async throws { try await TssModule.set_tss_tag(threshold_key: threshold_key, tss_tag: tss_tag) let nonce = String(try get_tss_nonce(threshold_key: threshold_key, tss_tag: tss_tag, prefetch: prefetch)) - let public_address = try await get_dkg_pub_key(threshold_key: threshold_key, tssTag: tss_tag, nonce: nonce, nodeDetails: nodeDetails, torusUtils: torusUtils) + let public_address = try await get_dkg_pub_key(threshold_key: threshold_key, tssTag: tss_tag, nonce: nonce) let pk_encoded = try JSONEncoder().encode(public_address) guard let public_key = String(data: pk_encoded, encoding: .utf8) else { throw RuntimeError("update_tss_pub_key - Conversion Error - ResultString") @@ -364,12 +364,12 @@ public final class TssModule { } } - private static func generate_tss_share(threshold_key: ThresholdKey, input_tss_share: String, tss_input_index: Int32, auth_signatures: [String], new_factor_pub: String, new_tss_index: Int32, selected_servers: [Int32]? = nil, completion: @escaping (Result) -> Void) { + private static func generate_tss_share(threshold_key: ThresholdKey, input_tss_share: String, tss_input_index: Int32, new_factor_pub: String, new_tss_index: Int32, selected_servers: [Int32]? = nil, completion: @escaping (Result) -> Void) { threshold_key.tkeyQueue.async { do { var errorCode: Int32 = -1 let curvePointer = UnsafeMutablePointer(mutating: (threshold_key.curveN as NSString).utf8String) - + let auth_signatures = try threshold_key.getAuthSignatures() let auth_signatures_json = try JSONSerialization.data(withJSONObject: auth_signatures) guard let auth_signatures_str = String(data: auth_signatures_json, encoding: .utf8) else { throw RuntimeError("auth signatures error") @@ -390,7 +390,7 @@ public final class TssModule { threshold_key_generate_tss_share(threshold_key.pointer, inputSharePointer, tss_input_index, new_tss_index, newFactorPubPointer, serversPointer, authSignaturesPointer, curvePointer, error) }) guard errorCode == 0 else { - throw RuntimeError("Error in ThresholdKey generate_tss_share") + throw RuntimeError("Error in ThresholdKey generate_tss_share : \(errorCode)") } completion(.success(())) } catch { @@ -399,14 +399,14 @@ public final class TssModule { } } - public static func generate_tss_share(threshold_key: ThresholdKey, tss_tag: String, input_tss_share: String, tss_input_index: Int32, auth_signatures: [String], new_factor_pub: String, new_tss_index: Int32, nodeDetails: AllNodeDetailsModel, torusUtils: TorusUtils, selected_servers: [Int32]? = nil) async throws { + public static func generate_tss_share(threshold_key: ThresholdKey, tss_tag: String, input_tss_share: String, tss_input_index: Int32, new_factor_pub: String, new_tss_index: Int32, selected_servers: [Int32]? = nil) async throws { try await TssModule.set_tss_tag(threshold_key: threshold_key, tss_tag: tss_tag) - try await update_tss_pub_key(threshold_key: threshold_key, tss_tag: tss_tag, nodeDetails: nodeDetails, torusUtils: torusUtils, prefetch: true) + try await update_tss_pub_key(threshold_key: threshold_key, tss_tag: tss_tag, prefetch: true) return try await withCheckedThrowingContinuation { continuation in - generate_tss_share(threshold_key: threshold_key, input_tss_share: input_tss_share, tss_input_index: tss_input_index, auth_signatures: auth_signatures, new_factor_pub: new_factor_pub, new_tss_index: new_tss_index) { + generate_tss_share(threshold_key: threshold_key, input_tss_share: input_tss_share, tss_input_index: tss_input_index, new_factor_pub: new_factor_pub, new_tss_index: new_tss_index) { result in switch result { case let .success(result): @@ -418,12 +418,13 @@ public final class TssModule { } } - private static func delete_tss_share(threshold_key: ThresholdKey, input_tss_share: String, tss_input_index: Int32, auth_signatures: [String], delete_factor_pub: String, selected_servers: [Int32]? = nil, completion: @escaping (Result) -> Void) { + private static func delete_tss_share(threshold_key: ThresholdKey, input_tss_share: String, tss_input_index: Int32, delete_factor_pub: String, selected_servers: [Int32]? = nil, completion: @escaping (Result) -> Void) { threshold_key.tkeyQueue.async { do { var errorCode: Int32 = -1 let curvePointer = UnsafeMutablePointer(mutating: (threshold_key.curveN as NSString).utf8String) - + + let auth_signatures = try threshold_key.getAuthSignatures() let auth_signatures_json = try JSONSerialization.data(withJSONObject: auth_signatures) guard let auth_signatures_str = String(data: auth_signatures_json, encoding: .utf8) else { throw RuntimeError("auth signatures error") @@ -444,7 +445,7 @@ public final class TssModule { threshold_key_delete_tss_share(threshold_key.pointer, inputSharePointer, tss_input_index, factorPubPointer, serversPointer, authSignaturesPointer, curvePointer, error) }) guard errorCode == 0 else { - throw RuntimeError("Error in ThresholdKey delete tss share") + throw RuntimeError("Error in ThresholdKey delete tss share : error code \(String(errorCode))") } completion(.success(())) } catch { @@ -453,13 +454,13 @@ public final class TssModule { } } - public static func delete_tss_share(threshold_key: ThresholdKey, tss_tag: String, input_tss_share: String, tss_input_index: Int32, auth_signatures: [String], delete_factor_pub: String, nodeDetails: AllNodeDetailsModel, torusUtils: TorusUtils, selected_servers: [Int32]? = nil) async throws { - try await update_tss_pub_key(threshold_key: threshold_key, tss_tag: tss_tag, nodeDetails: nodeDetails, torusUtils: torusUtils, prefetch: true) + public static func delete_tss_share(threshold_key: ThresholdKey, tss_tag: String, input_tss_share: String, tss_input_index: Int32, delete_factor_pub: String, selected_servers: [Int32]? = nil) async throws { + try await update_tss_pub_key(threshold_key: threshold_key, tss_tag: tss_tag, prefetch: true) try await TssModule.set_tss_tag(threshold_key: threshold_key, tss_tag: tss_tag) return try await withCheckedThrowingContinuation { continuation in - delete_tss_share(threshold_key: threshold_key, input_tss_share: input_tss_share, tss_input_index: tss_input_index, auth_signatures: auth_signatures, delete_factor_pub: delete_factor_pub) { + delete_tss_share(threshold_key: threshold_key, input_tss_share: input_tss_share, tss_input_index: tss_input_index, delete_factor_pub: delete_factor_pub) { result in switch result { case let .success(result): @@ -471,6 +472,18 @@ public final class TssModule { } } + public static func register_factor (threshold_key: ThresholdKey, tss_tag: String, factor_key: String, new_factor_pub: String, new_tss_index: Int32, selected_servers: [Int32]? = nil ) async throws { + if factor_key.count > 66 { throw RuntimeError("Invalid factor Key") } + try await TssModule.set_tss_tag(threshold_key: threshold_key, tss_tag: tss_tag) + + let (tss_index, tss_share ) = try await get_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, factorKey: factor_key) + if tss_index == String(new_tss_index) { + try await copy_factor_pub(threshold_key: threshold_key, tss_tag: tss_tag, factorKey: factor_key, newFactorPub: new_factor_pub, tss_index: new_tss_index) + } else { + try await TssModule.generate_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, input_tss_share: tss_share, tss_input_index: Int32(tss_index)!, new_factor_pub: new_factor_pub, new_tss_index: new_tss_index, selected_servers: selected_servers) + } + } + /// Generate tss_index's share and register to new factor key /// - Parameters: /// - threshold_key: The threshold key to act on. @@ -484,12 +497,12 @@ public final class TssModule { /// - torusUtils: torus utils /// /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key. - public static func add_factor_pub(threshold_key: ThresholdKey, tss_tag: String, factor_key: String, auth_signatures: [String], new_factor_pub: String, new_tss_index: Int32, selected_servers: [Int32]? = nil, nodeDetails: AllNodeDetailsModel, torusUtils: TorusUtils) async throws { + public static func add_factor_pub(threshold_key: ThresholdKey, tss_tag: String, factor_key: String, new_factor_pub: String, new_tss_index: Int32, selected_servers: [Int32]? = nil) async throws { if factor_key.count > 66 { throw RuntimeError("Invalid factor Key") } try await TssModule.set_tss_tag(threshold_key: threshold_key, tss_tag: tss_tag) let (tss_index, tss_share) = try await get_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, factorKey: factor_key) - try await TssModule.generate_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, input_tss_share: tss_share, tss_input_index: Int32(tss_index)!, auth_signatures: auth_signatures, new_factor_pub: new_factor_pub, new_tss_index: new_tss_index, nodeDetails: nodeDetails, torusUtils: torusUtils, selected_servers: selected_servers) + try await TssModule.generate_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, input_tss_share: tss_share, tss_input_index: Int32(tss_index)!, new_factor_pub: new_factor_pub, new_tss_index: new_tss_index, selected_servers: selected_servers) } /// Delete factor pub from tss metadata @@ -504,12 +517,12 @@ public final class TssModule { /// - selected_servers: node indexes of the server that will be communicated to /// /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key. - public static func delete_factor_pub(threshold_key: ThresholdKey, tss_tag: String, factor_key: String, auth_signatures: [String], delete_factor_pub: String, nodeDetails: AllNodeDetailsModel, torusUtils: TorusUtils, selected_servers: [Int32]? = nil) async throws { + public static func delete_factor_pub(threshold_key: ThresholdKey, tss_tag: String, factor_key: String, delete_factor_pub: String, selected_servers: [Int32]? = nil) async throws { if factor_key.count > 66 { throw RuntimeError("Invalid factor Key") } try await TssModule.set_tss_tag(threshold_key: threshold_key, tss_tag: tss_tag) let (tss_index, tss_share) = try await get_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, factorKey: factor_key) - try await TssModule.delete_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, input_tss_share: tss_share, tss_input_index: Int32(tss_index)!, auth_signatures: auth_signatures, delete_factor_pub: delete_factor_pub, nodeDetails: nodeDetails, torusUtils: torusUtils, selected_servers: selected_servers) + try await TssModule.delete_tss_share(threshold_key: threshold_key, tss_tag: tss_tag, input_tss_share: tss_share, tss_input_index: Int32(tss_index)!, delete_factor_pub: delete_factor_pub, selected_servers: selected_servers) } /// Backup device share with factor key @@ -540,22 +553,39 @@ public final class TssModule { /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key. public static func find_device_share_index(threshold_key: ThresholdKey, factor_key: String) async throws -> String { let result = try await threshold_key.storage_layer_get_metadata(private_key: factor_key) + guard let resultData = result.data(using: .utf8) else { throw "Invalid factor key" } guard let resultJson = try JSONSerialization.jsonObject(with: resultData) as? [String: Any] else { throw "Invalid factor key" } - guard let deviceShareJson = resultJson["deviceShare"] as? [String: Any] else { - throw "Invalid factor key" - } - guard let shareJson = deviceShareJson["share"] as? [String: Any] else { - throw "Invalid factor key" - } - guard let shareIndex = shareJson["shareIndex"] as? String else { - throw "Invalid factor key" - } - return shareIndex + + let shareStore = try ShareStore(json: result) + return try shareStore.share_index() +// +// // TODO : Fix the deserialization of the Factor cloud metadata +// // ts implemetation break this +// let deviceShareJson : [String: Any] +// if resultJson["deviceShare"] != nil { +// guard var deviceShare = resultJson["deviceShare"] as? [String: Any] else { +// throw "invalid factor json without deviceShare or share indexkey" +// } +// deviceShareJson = deviceShare +// } else { +// guard let deviceShare = resultJson["share"] as? [String:Any] else { +// throw "invalid factor json without deviceShare or share indexkey" +// } +// deviceShareJson = deviceShare +// } +// +// guard let shareJson = deviceShareJson["share"] as? [String: Any] else { +// throw "Invalid factor key" +// } +// guard let shareIndex = shareJson["shareIndex"] as? String else { +// throw "Invalid factor key" +// } +// return shareIndex } /// Function to get dkg public key @@ -568,9 +598,11 @@ public final class TssModule { /// - Returns: `TSSPubKeyResult` /// /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key. - public static func get_dkg_pub_key(threshold_key: ThresholdKey, tssTag: String, nonce: String, nodeDetails: AllNodeDetailsModel, torusUtils: TorusUtils) async throws -> TSSPubKeyResult { + public static func get_dkg_pub_key(threshold_key: ThresholdKey, tssTag: String, nonce: String) async throws -> TSSPubKeyResult { let extendedVerifierId = try threshold_key.get_extended_verifier_id() let split = extendedVerifierId.components(separatedBy: "\u{001c}") + let torusUtils = try threshold_key.getTorusUtils() + let nodeDetails = try threshold_key.getnodeDetails() let result = try await torusUtils.getPublicAddress(endpoints: nodeDetails.torusNodeEndpoints, torusNodePubs: nodeDetails.torusNodePub, verifier: split[0], verifierId: split[1], extendedVerifierId: "\(split[1])\u{0015}\(tssTag)\u{0016}\(nonce)") diff --git a/Sources/ThresholdKey/Modules/TssSecurityQuestionModule.swift b/Sources/ThresholdKey/Modules/TssSecurityQuestionModule.swift new file mode 100644 index 0000000..a44388f --- /dev/null +++ b/Sources/ThresholdKey/Modules/TssSecurityQuestionModule.swift @@ -0,0 +1,228 @@ +import Foundation +#if canImport(lib) + import lib +#endif +import BigInt + +let TssSecurityQuestion = "tssSecurityQuestion" + + +public struct EncryptedMessage : Codable { + public let ciphertext: String; + public let ephemPublicKey: String; + public let iv: String; + public let mac: String; + + public func toString() throws -> String { + let data = try JSONEncoder().encode(self) +// let data = try JSONSerialization.data(withJSONObject: self) + guard let result = String(data: data, encoding: .utf8) else { + throw "invalid toString" + } + return result + } +} + +public struct TssSecurityQuestionData : Codable{ + public var shareIndex: String + public var factorPublicKey: String + public var question: String + + public func toJsonString() throws -> String { + let jsonData = try JSONEncoder().encode(self) + guard let jsonStr = String(data: jsonData, encoding: .utf8) else { + throw "Invalid security question data" + } + return jsonStr + } + + public static func fromJsonString(jsonStr: String ) throws -> Self { + guard let data = jsonStr.data(using: .utf8) else { + throw "invalid security question data" + } + let store = try JSONDecoder().decode( TssSecurityQuestionData.self, from: data) + return store + } +} + +// Security question has low entrophy, hence it is not recommended way to secure the factor key or share +public final class TssSecurityQuestionModule { + public static func compute_hash( threshold : ThresholdKey, answer: String, tag: String ) throws -> String { + let suffix = try threshold.get_key_details().pub_key.getPublicKey(format: .EllipticCompress) + tag + let prehash = answer + suffix + guard let hash = prehash.data(using: .utf8)?.sha3(.keccak256) else { + throw "Invalid answer format for answer : \(answer)" + } + return hash.toHexString() + } + + /// set security question + /// - Parameters: + /// - threshold_key: The threshold key to act on. + /// - question: The security question. + /// - answer: The answer for the security question. + /// - factorKey: Factor key that registred to security question + /// - tag: tss tag + /// + /// - Returns: `` + /// + /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key of failed to set security question + public static func set_security_question( threshold : ThresholdKey, question: String, answer: String, factorKey :String, tag: String ) async throws -> String { + + let domainKey = TssSecurityQuestion + ":" + tag + + var isSet = false + do { + let question = try TssSecurityQuestionModule.get_question(threshold: threshold, tag: tag) + if question.count > 0 { + isSet = true + } + } catch {} + + if isSet {throw "Trying to set Security Question again"} + + let hash = try compute_hash(threshold: threshold, answer: answer, tag: tag) + + let hashKey = PrivateKey(hex: hash) + let hashPub = try hashKey.toPublic(); + + let shareIndex: Int32 = 3 + + let data = TssSecurityQuestionData( shareIndex: String(shareIndex), factorPublicKey: hashPub, question: question ) + try threshold.set_general_store_domain(key: domainKey, data: data.toJsonString() ) + + // register + try await TssModule.register_factor(threshold_key: threshold, tss_tag: tag, factor_key: factorKey, new_factor_pub: hashPub, new_tss_index: shareIndex) + let deviceShareIndex = try await TssModule.find_device_share_index(threshold_key: threshold, factor_key: factorKey) + + try TssModule.backup_share_with_factor_key(threshold_key: threshold, shareIndex: deviceShareIndex, factorKey: hash) + + return hash + } + + + /// change security question to new question and answer + /// - Parameters: + /// - threshold_key: The threshold key to act on. + /// - newQuestion: The new security question . + /// - newAnswer: The new answer for the security question. + /// - answer: current answer + /// - tag: tss tag + /// + /// - Returns: `` + /// + /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key or fail to change security question + public static func change_security_question( threshold : ThresholdKey, newQuestion: String, newAnswer: String, answer: String, tag: String) async throws -> (String, String){ + let domainKey = TssSecurityQuestion + ":" + tag + + let storeStr = try threshold.get_general_store_domain(key: domainKey) + var store = try TssSecurityQuestionData.fromJsonString(jsonStr: storeStr) + + // hash answer and new answer + let hash = try compute_hash(threshold: threshold, answer: answer, tag: tag) + let newHash = try compute_hash(threshold: threshold, answer: newAnswer, tag: tag) + + let hashKey = PrivateKey(hex: hash) + let hashPub = try hashKey.toPublic(); + + let newHashKey = PrivateKey(hex: newHash) + let newHashPub = try newHashKey.toPublic(); + + // create new factor using newHash + let (tssIndex, _) = try await TssModule.get_tss_share(threshold_key: threshold, tss_tag: tag, factorKey: hash) + try await TssModule.register_factor(threshold_key: threshold, tss_tag: tag, factor_key: hash, new_factor_pub: newHashPub, new_tss_index: Int32(tssIndex)!) + let deviceShareIndex = try await TssModule.find_device_share_index(threshold_key: threshold, factor_key: hash) + try TssModule.backup_share_with_factor_key(threshold_key: threshold, shareIndex: deviceShareIndex, factorKey: newHash) + + // delete old hash factor + try await TssModule.delete_factor_pub(threshold_key: threshold, tss_tag: tag, factor_key: hash, delete_factor_pub: hashPub) + // delete share metadata + + + store.question = newQuestion + store.factorPublicKey = newHashPub + + // set updated data to domain store + let jsonStr = try store.toJsonString() + try threshold.set_general_store_domain(key: domainKey, data: jsonStr ) + + try await threshold.sync_metadata(); + + return (hash, newHash) + } + + /// delete security question + /// - Parameters: + /// - threshold_key: The threshold key to act on. + /// - tag: tss tag + /// + /// - Returns: `String` public key of the factor + /// + /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key or fail to delete security question + public static func delete_security_question( threshold : ThresholdKey, tag: String, factorKey: String) async throws -> String { + // + let domainKey = TssSecurityQuestion + ":" + tag + + let jsonStr = try threshold.get_general_store_domain(key: domainKey) + let jsonObj = try TssSecurityQuestionData.fromJsonString(jsonStr: jsonStr) + + if jsonObj.question.count == 0 { + throw "Security Question is not set" + } + let deleteFactorPub = jsonObj.factorPublicKey + + // replace with delete store domain + // sync_metadata is not required as delete_factor_pub will sync metadata + let jsonStr1 = "{}" + try threshold.set_general_store_domain(key: domainKey, data: jsonStr1 ) + + try await TssModule.delete_factor_pub(threshold_key: threshold, tss_tag: tag, factor_key: factorKey, delete_factor_pub: deleteFactorPub) + + return deleteFactorPub + } + + + /// get security question + /// - Parameters: + /// - threshold_key: The threshold key to act on. + /// - tag: tss tag + /// + /// - Returns: `String` question + /// + /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key or fail to get security question + public static func get_question( threshold: ThresholdKey, tag: String ) throws -> String { + // get data format from json + let domainKey = TssSecurityQuestion + ":" + tag + + let jsonStr = try threshold.get_general_store_domain(key: domainKey) + let jsonObj = try TssSecurityQuestionData.fromJsonString(jsonStr: jsonStr) + + return jsonObj.question + } + + + /// recover security question's factor given correct answer + /// - Parameters: + /// - threshold_key: The threshold key to act on. + /// - answer: answer to security question + /// - tag: tss tag + /// + /// - Returns: `String` factor key + /// + /// - Throws: `RuntimeError`, indicates invalid parameters was used or invalid threshold key or fail to delete security question + public static func recover_factor ( threshold: ThresholdKey, answer: String , tag: String ) throws -> String { + // get data format from json + let domainKey = TssSecurityQuestion + ":" + tag + let jsonStr = try threshold.get_general_store_domain(key: domainKey) + let store = try TssSecurityQuestionData.fromJsonString(jsonStr: jsonStr) + + // hash answer + let hash = try compute_hash(threshold: threshold, answer: answer, tag: tag) + let factorPub = try PrivateKey(hex: hash).toPublic(format: .EllipticCompress); + if (factorPub != store.factorPublicKey) { + throw "Invalid Answer" + } + + return hash + } +} diff --git a/Sources/ThresholdKey/StorageLayer.swift b/Sources/ThresholdKey/StorageLayer.swift index 92ce5ee..1d87e45 100644 --- a/Sources/ThresholdKey/StorageLayer.swift +++ b/Sources/ThresholdKey/StorageLayer.swift @@ -4,6 +4,21 @@ import Foundation #endif +func multipartData (finalMetadataParams: [[String: Any]] , boundary: String) -> Data { + // Create a FormData-like structure in Swift + var formData = Data() + for (key, value) in finalMetadataParams.enumerated() { + if let jsonValue = try? JSONSerialization.data(withJSONObject: value) { + formData.append("--\(boundary)\r\n".data(using: .utf8)!) + formData.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!) + formData.append(jsonValue) + formData.append("\r\n".data(using: .utf8)!) + } + } + formData.append("--\(boundary)--".data(using: .utf8)!) + return formData +} + public final class StorageLayer { private(set) var pointer: OpaquePointer? @@ -51,29 +66,12 @@ public final class StorageLayer { request.addValue("Content-Type", forHTTPHeaderField: "Access-Control-Allow-Headers") if urlString.split(separator: "/").last == "bulk_set_stream" { - // let boundary = UUID().uuidString; - // request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - + let boundary = UUID().uuidString; + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let json = try! JSONSerialization.jsonObject(with: dataString.data(using: String.Encoding.utf8)!, options: .allowFragments) as! [[String: Any]] - - // for item in json { - // let dataItem = try! JSONSerialization.data(withJSONObject: item, options: .prettyPrinted) - // requestData.append(StorageLayer.createMultipartBody(data: dataItem, boundary: boundary, file: "multipartData")) - // } - - var form_data: [String] = [] - - // urlencoded item format: "(key)=(self.percentEscapeString(value))" - for (index, element) in json.enumerated() { - let json_elem = try! JSONSerialization.data(withJSONObject: element, options: .withoutEscapingSlashes) - let json_escaped_string = StorageLayer.percentEscapeString(string: String(data: json_elem, encoding: .utf8)!) - let final_string = String(index) + "=" + json_escaped_string - form_data.append(final_string) - } - let body_data = form_data.joined(separator: "&") - - request.httpBody = body_data.data(using: String.Encoding.utf8) + let formData = multipartData(finalMetadataParams: json, boundary: boundary) + request.httpBody = formData } else { request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = dataString.data(using: String.Encoding.utf8) diff --git a/Sources/ThresholdKey/ThresholdKey.swift b/Sources/ThresholdKey/ThresholdKey.swift index aaf3721..8f90873 100644 --- a/Sources/ThresholdKey/ThresholdKey.swift +++ b/Sources/ThresholdKey/ThresholdKey.swift @@ -10,7 +10,40 @@ public class ThresholdKey { private(set) var use_tss: Bool = false internal let curveN = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" internal let tkeyQueue = DispatchQueue(label: "thresholdkey.queue") + internal var authSignatures: [String]? + internal var nodeDetails: AllNodeDetailsModel? + internal var torusUtils: TorusUtils? + + public func getAuthSignatures () throws -> [String] { + guard let result = self.authSignatures else { + throw "authSignatures is undefined" + } + return result + } + public func getnodeDetails () throws -> AllNodeDetailsModel { + guard let result = self.nodeDetails else { + throw "authSignatures is undefined" + } + return result + } + public func getTorusUtils () throws -> TorusUtils { + guard let result = self.torusUtils else { + throw "authSignatures is undefined" + } + return result + } + + public func setAuthSignatures ( authSignatures: [String]) { + self.authSignatures = authSignatures + } + public func setnodeDetails (nodeDetails : AllNodeDetailsModel) { + self.nodeDetails = nodeDetails + } + public func setTorusUtils (torusUtils : TorusUtils) { + self.torusUtils = torusUtils + } + /// Instantiate a `ThresholdKey` object, /// /// - Parameters: @@ -108,7 +141,7 @@ public class ThresholdKey { let ptr = withUnsafeMutablePointer(to: &device_index, { tssDeviceIndexPointer in withUnsafeMutablePointer(to: &errorCode, { error in threshold_key_initialize(self.pointer, keyPointer, storePtr, neverInitializeNewKey, includeLocalMetadataTransitions, false, curvePointer, useTss, nil, tssDeviceIndexPointer, nil, error) }) }) guard errorCode == 0 else { - throw RuntimeError("Error in ThresholdKey Initialize") + throw RuntimeError("Error in ThresholdKey Initialize \(errorCode)") } let result = try! KeyDetails(pointer: ptr!) completion(.success(result)) @@ -524,7 +557,7 @@ public class ThresholdKey { threshold_key_input_factor_key(self.pointer, cFactorKey, error) }) guard errorCode == 0 else { - throw RuntimeError("Error in ThresholdKey input_factor_key") + throw RuntimeError("Error in ThresholdKey input_factor_key \(errorCode)") } completion(.success(())) } catch { @@ -554,6 +587,48 @@ public class ThresholdKey { } } + + private func patch_input_factor_key(factorKey: String, completion: @escaping (Result) -> Void) { + tkeyQueue.async { + do { + var errorCode: Int32 = -1 + let cFactorKey = UnsafeMutablePointer(mutating: (factorKey as NSString).utf8String) + let curvePointer = UnsafeMutablePointer(mutating: (self.curveN as NSString).utf8String) + + withUnsafeMutablePointer(to: &errorCode, { error in + threshold_key_patch_input_factor_key(self.pointer, cFactorKey, curvePointer, error) + }) + guard errorCode == 0 else { + throw RuntimeError("Error in ThresholdKey input_factor_key \(errorCode)") + } + completion(.success(())) + } catch { + completion(.failure(error)) + } + } + } + + /// Patch and Inserts a `ShareStore` into `ThresholdKey` using `FactorKey`, useful for insertion before reconstruction to ensure the number of shares meet the minimum threshold. + /// + /// - Parameters: + /// - factorKey : The `factorKey` to be inserted + /// + /// - Throws: `RuntimeError`, indicates invalid parameters or invalid `ThresholdKey`. + public func patch_input_factor_key(factorKey: String) async throws { + return try await withCheckedThrowingContinuation { + continuation in + self.patch_input_factor_key(factorKey: factorKey) { + result in + switch result { + case let .success(result): + continuation.resume(returning: result) + case let .failure(error): + continuation.resume(throwing: error) + } + } + } + } + /// Retrieves all share indexes for a `ThresholdKey`. /// /// - Returns: Array of String @@ -716,6 +791,78 @@ public class ThresholdKey { let json = try! JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: .allowFragments) as! [String: Any] return json } + + + /// set the general store domain. + /// + /// - Parameters: + /// - key: key domain to be stored + /// - data: json string data t be stored + /// + /// - Throws: `RuntimeError`, indicates invalid parameters or invalid `ThresholdKey`. + public func set_general_store_domain( key: String, data: String) throws { + var errorCode: Int32 = -1 + let keyPointer = UnsafeMutablePointer(mutating: (key as NSString).utf8String) + + let dataPointer = UnsafeMutablePointer(mutating: (data as NSString).utf8String) + + withUnsafeMutablePointer(to: &errorCode, { error in + threshold_key_set_general_store_domain(pointer, keyPointer, dataPointer, error) + }) + guard errorCode == 0 else { + throw RuntimeError("Error in ThresholdKey set_domain_store_item : error Code : \(errorCode)") + } + } + + + + /// Returns the general store domain. + /// + /// - Parameters: + /// - key: key domain stored + /// + /// - Returns: `String` json_string + /// + /// - Throws: `RuntimeError`, indicates invalid parameters or invalid `ThresholdKey`. + public func get_general_store_domain(key: String) throws -> String { + var errorCode: Int32 = -1 + let keyPointer = UnsafeMutablePointer(mutating: (key as NSString).utf8String) + + let result = withUnsafeMutablePointer(to: &errorCode, { error in + threshold_key_get_general_store_domain(pointer, keyPointer, error) + }) + guard errorCode == 0 else { + throw RuntimeError("Error in ThresholdKey get_domain_store_item \(errorCode)") + } + let string = String(cString: result!) + string_free(result) + + return string + } + + + /// delete the general store domain. + /// + /// - Parameters: + /// - key: key domain to be deleted + /// + /// - Returns: `String` json_string + /// + /// - Throws: `RuntimeError`, indicates invalid parameters or invalid `ThresholdKey`. + public func delete_general_store_domain(key: String) throws { + var errorCode: Int32 = -1 + let keyPointer = UnsafeMutablePointer(mutating: (key as NSString).utf8String) + + let result = withUnsafeMutablePointer(to: &errorCode, { error in + threshold_key_get_general_store_domain(pointer, keyPointer, error) + }) + guard errorCode == 0 else { + throw RuntimeError("Error in ThresholdKey get_domain_store_item") + } + let string = String(cString: result!) + string_free(result) + } + /// Returns all shares according to their mapping. /// @@ -753,6 +900,26 @@ public class ThresholdKey { } } } + + private func sync_metadata(completion: @escaping (Result) -> Void) { + tkeyQueue.async { + do { + var errorCode: Int32 = -1 + + let curvePointer = UnsafeMutablePointer(mutating: NSString(string: self.curveN).utf8String) + + withUnsafeMutablePointer(to: &errorCode, { error in + threshold_key_sync_metadata(self.pointer, curvePointer, error) + }) + guard errorCode == 0 else { + throw RuntimeError("Error in ThresholdKey sync_local_metadata_transistions") + } + completion(.success(())) + } catch { + completion(.failure(error)) + } + } + } /// Syncronises metadata transitions, only used if manual sync is enabled. /// @@ -771,6 +938,24 @@ public class ThresholdKey { } } } + + /// create metadata transitions (to all shares), sync to server if manual sync is false. + /// + /// - Throws: `RuntimeError`, indicates invalid parameters or invalid `ThresholdKey`. + public func sync_metadata() async throws { + return try await withCheckedThrowingContinuation { + continuation in + self.sync_metadata { + result in + switch result { + case let .success(result): + continuation.resume(returning: result) + case let .failure(error): + continuation.resume(throwing: error) + } + } + } + } /// Returns all shares descriptions. /// diff --git a/Sources/libtkey/include/tkey.h b/Sources/libtkey/include/tkey.h index 809cda3..de37e66 100644 --- a/Sources/libtkey/include/tkey.h +++ b/Sources/libtkey/include/tkey.h @@ -36,6 +36,8 @@ void string_free(char *ptr); char* generate_private_key( char* curve_n, int* error_code); char* private_to_public( char* secret, int* error_code); + char* tkey_encrypt(char* public_key, char* data, char* curve_n, int* error_code); + char* tkey_decrypt(char* secret_key, char* data, int* error_code); struct Polynomial* lagrange_interpolate_polynomial(struct KeyPointArray* points, char* curve_n, int* error_code); char* key_point_get_x(struct KeyPoint* point, int* error_code); struct KeyPoint* key_point_new(char* x, char* y, int* error_code); @@ -85,9 +87,14 @@ char* threshold_key_output_share(struct FFIThresholdKey* threshold_key, char* share_index, char* share_type, char* curve_n, int* error_code); char* threshold_key_get_tkey_store(struct FFIThresholdKey* threshold_key, char* module_name, int* error_code); char* threshold_key_get_tkey_store_item(struct FFIThresholdKey* threshold_key, char* module_name, char* identifier, int* error_code); + void threshold_key_set_general_store_domain(struct FFIThresholdKey* threshold_key, char* key, char* json_data, int* error_code); + char* threshold_key_get_general_store_domain(struct FFIThresholdKey* threshold_key, char* key, int* error_code); + + void threshold_key_input_share(struct FFIThresholdKey* threshold_key, char* share, char* share_type, char* curve_n, int* error_code); struct ShareStore* threshold_key_output_share_store(struct FFIThresholdKey* threshold_key, char* share_index, char* poly_id, char* curve_n, int* error_code); void threshold_key_input_share_store(struct FFIThresholdKey* threshold_key, struct ShareStore* share_store, int* error_code); + void threshold_key_patch_input_factor_key(struct FFIThresholdKey* threshold_key, char* factor_key, char* curve_n, int* error_code); void threshold_key_input_factor_key(struct FFIThresholdKey* threshold_key, char* factor_key, int* error_code); char* threshold_key_get_shares_indexes(struct FFIThresholdKey* threshold_key, int* error_code); char* threshold_key_encrypt(struct FFIThresholdKey* threshold_key, char* data, char* curve_n, int* error_code); @@ -97,6 +104,7 @@ struct Polynomial* threshold_key_reconstruct_latest_poly(struct FFIThresholdKey *threshold_key, char* curve_n, int* error_code); struct Metadata* threshold_key_get_last_fetched_cloud_metadata(struct FFIThresholdKey* threshold_key, int* error_code); void threshold_key_sync_local_metadata_transitions(struct FFIThresholdKey *threshold_key, char* curve_n, int* error_code); + void threshold_key_sync_metadata(struct FFIThresholdKey *threshold_key, char* curve_n, int* error_code); struct ShareStoreArray* threshold_key_get_all_share_stores_for_latest_polynomial(struct FFIThresholdKey* threshold_key, char* curve_n, int* error_code); struct ShareStorePolyIDShareIndexMap* threshold_key_get_shares(struct FFIThresholdKey* threshold_key, int* error_code); diff --git a/Sources/libtkey/libtkey.xcframework/Info.plist b/Sources/libtkey/libtkey.xcframework/Info.plist index 51ab6cd..dd0cadd 100644 --- a/Sources/libtkey/libtkey.xcframework/Info.plist +++ b/Sources/libtkey/libtkey.xcframework/Info.plist @@ -6,30 +6,30 @@ LibraryIdentifier - ios-arm64_x86_64-simulator + ios-arm64 LibraryPath libtkey.a SupportedArchitectures arm64 - x86_64 SupportedPlatform ios - SupportedPlatformVariant - simulator LibraryIdentifier - ios-arm64 + ios-arm64_x86_64-simulator LibraryPath libtkey.a SupportedArchitectures arm64 + x86_64 SupportedPlatform ios + SupportedPlatformVariant + simulator CFBundlePackageType diff --git a/Sources/libtkey/libtkey.xcframework/ios-arm64-simulator/libtkey.a b/Sources/libtkey/libtkey.xcframework/ios-arm64-simulator/libtkey.a new file mode 100644 index 0000000..6bdca65 Binary files /dev/null and b/Sources/libtkey/libtkey.xcframework/ios-arm64-simulator/libtkey.a differ diff --git a/Sources/libtkey/libtkey.xcframework/ios-arm64/libtkey.a b/Sources/libtkey/libtkey.xcframework/ios-arm64/libtkey.a index 4d89a2b..4e8fcc0 100644 Binary files a/Sources/libtkey/libtkey.xcframework/ios-arm64/libtkey.a and b/Sources/libtkey/libtkey.xcframework/ios-arm64/libtkey.a differ diff --git a/Sources/libtkey/libtkey.xcframework/ios-arm64_x86_64-simulator/libtkey.a b/Sources/libtkey/libtkey.xcframework/ios-arm64_x86_64-simulator/libtkey.a index ab241f2..0fe8218 100644 Binary files a/Sources/libtkey/libtkey.xcframework/ios-arm64_x86_64-simulator/libtkey.a and b/Sources/libtkey/libtkey.xcframework/ios-arm64_x86_64-simulator/libtkey.a differ diff --git a/Tests/tkeypkgTests/IntegrationTests.swift b/Tests/tkeypkgTests/IntegrationTests.swift index c7e8ca6..d13f896 100644 --- a/Tests/tkeypkgTests/IntegrationTests.swift +++ b/Tests/tkeypkgTests/IntegrationTests.swift @@ -37,6 +37,11 @@ final class integrationTests: XCTestCase { manual_sync: false, rss_comm: rss_comm ) + + // setting variables needed for tss operations + threshold.setAuthSignatures(authSignatures: signatures) + threshold.setnodeDetails(nodeDetails: nodeDetail) + threshold.setTorusUtils(torusUtils: torusUtils) _ = try! await threshold.initialize() _ = try! await threshold.reconstruct() @@ -48,7 +53,7 @@ final class integrationTests: XCTestCase { let factorPub = try factorKey.toPublic() try TssModule.backup_share_with_factor_key(threshold_key: threshold, shareIndex: shareIndex.hex, factorKey: factorKey.hex) - try await TssModule.create_tagged_tss_share(threshold_key: threshold, tss_tag: tssTag, deviceTssShare: nil, factorPub: factorPub, deviceTssIndex: 2, nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.create_tagged_tss_share(threshold_key: threshold, tss_tag: tssTag, deviceTssShare: nil, factorPub: factorPub, deviceTssIndex: 2 ) let (tss_index, tss_share) = try await TssModule.get_tss_share(threshold_key: threshold, tss_tag: tssTag, factorKey: factorKey.hex) @@ -57,7 +62,7 @@ final class integrationTests: XCTestCase { let newFactorKey = try PrivateKey.generate() let newFactorPub = try newFactorKey.toPublic() // 2/2 -> 2/3 tss - try await TssModule.generate_tss_share(threshold_key: threshold, tss_tag: tssTag, input_tss_share: tss_share, tss_input_index: Int32(tss_index)!, auth_signatures: signatures, new_factor_pub: newFactorPub, new_tss_index: 3, nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.generate_tss_share(threshold_key: threshold, tss_tag: tssTag, input_tss_share: tss_share, tss_input_index: Int32(tss_index)!, new_factor_pub: newFactorPub, new_tss_index: 3) let (tss_index3, tss_share3) = try await TssModule.get_tss_share(threshold_key: threshold, tss_tag: tssTag, factorKey: newFactorKey.hex) let (_, tss_share_updated) = try await TssModule.get_tss_share(threshold_key: threshold, tss_tag: tssTag, factorKey: factorKey.hex) @@ -73,6 +78,12 @@ final class integrationTests: XCTestCase { enable_logging: true, manual_sync: false ) + + // setting variables needed for tss operations + threshold2.setAuthSignatures(authSignatures: signatures) + threshold2.setnodeDetails(nodeDetails: nodeDetail) + threshold2.setTorusUtils(torusUtils: torusUtils) + _ = try! await threshold2.initialize() try await threshold2.input_factor_key(factorKey: factorKey.hex) @@ -91,7 +102,7 @@ final class integrationTests: XCTestCase { XCTAssertEqual(tss_index3, tss_index2_3) // 2/3 -> 2/2 tss - try await TssModule.delete_tss_share(threshold_key: threshold, tss_tag: tssTag, input_tss_share: tss_share3, tss_input_index: Int32(tss_index3)!, auth_signatures: signatures, delete_factor_pub: newFactorPub, nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.delete_tss_share(threshold_key: threshold, tss_tag: tssTag, input_tss_share: tss_share3, tss_input_index: Int32(tss_index3)!, delete_factor_pub: newFactorPub) // XCTAssertThrowsError( try await TssModule.get_tss_share(threshold_key: threshold, tss_tag: tssTag, factorKey: newFactorKey.hex) ) let (tss_index_updated2, tss_share_updated2) = try await TssModule.get_tss_share(threshold_key: threshold, tss_tag: tssTag, factorKey: factorKey.hex) @@ -101,10 +112,10 @@ final class integrationTests: XCTestCase { XCTAssertNotEqual(tss_share_updated, tss_share_updated2) // 2/2 -> 2/3 tss - try await TssModule.add_factor_pub(threshold_key: threshold, tss_tag: tssTag, factor_key: factorKey.hex, auth_signatures: signatures, new_factor_pub: newFactorPub, new_tss_index: 3, nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.add_factor_pub(threshold_key: threshold, tss_tag: tssTag, factor_key: factorKey.hex, new_factor_pub: newFactorPub, new_tss_index: 3) // 2/3 -> 2/2 tss - try await TssModule.delete_factor_pub(threshold_key: threshold, tss_tag: tssTag, factor_key: factorKey.hex, auth_signatures: signatures, delete_factor_pub: newFactorPub, nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.delete_factor_pub(threshold_key: threshold, tss_tag: tssTag, factor_key: factorKey.hex, delete_factor_pub: newFactorPub ) } func test_TssModule_multi_tag() async throws { @@ -142,6 +153,11 @@ final class integrationTests: XCTestCase { manual_sync: true, rss_comm: rss_comm ) + + // setting variables needed for tss operations + threshold.setAuthSignatures(authSignatures: signatures) + threshold.setnodeDetails(nodeDetails: nodeDetail) + threshold.setTorusUtils(torusUtils: torusUtils) _ = try! await threshold.initialize() _ = try! await threshold.reconstruct() @@ -149,7 +165,7 @@ final class integrationTests: XCTestCase { let share = try threshold.output_share(shareIndex: shareIndex.hex) print(share) - let testTags = ["tag1", "tag2", "tag3", "tag4", "tag5"] + let testTags = ["tag1", "tag2", "tag3", "tag4"] var tssMods: [(ThresholdKey, String)] = [] @@ -171,7 +187,7 @@ final class integrationTests: XCTestCase { factorPubs.append(factorPub) try await TssModule.set_tss_tag(threshold_key: threshold, tss_tag: tag) - try await TssModule.create_tagged_tss_share(threshold_key: threshold, tss_tag: tag, deviceTssShare: nil, factorPub: factorPub, deviceTssIndex: 2, nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.create_tagged_tss_share(threshold_key: threshold, tss_tag: tag, deviceTssShare: nil, factorPub: factorPub, deviceTssIndex: 2) let (tssIndex, tssShare) = try! await TssModule.get_tss_share(threshold_key: threshold, tss_tag: tag, factorKey: factorKey.hex) tssIndexes.append(tssIndex) @@ -188,7 +204,7 @@ final class integrationTests: XCTestCase { newFactorKeys.append(newFactorKey) newFactorPubs.append(newFactorPub) - try await TssModule.add_factor_pub(threshold_key: threshold, tss_tag: tag, factor_key: factorKeys[index].hex, auth_signatures: signatures, new_factor_pub: newFactorPub, new_tss_index: 3, nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.add_factor_pub(threshold_key: threshold, tss_tag: tag, factor_key: factorKeys[index].hex, new_factor_pub: newFactorPub, new_tss_index: 3) try await threshold.sync_local_metadata_transistions() @@ -235,6 +251,11 @@ final class integrationTests: XCTestCase { enable_logging: true, manual_sync: false ) + // setting variables needed for tss operations + threshold2.setAuthSignatures(authSignatures: signatures) + threshold2.setnodeDetails(nodeDetails: nodeDetail) + threshold2.setTorusUtils(torusUtils: torusUtils) + _ = try! await threshold2.initialize() try! await threshold2.input_share(share: share, shareType: nil) _ = try! await threshold2.reconstruct() @@ -255,7 +276,7 @@ final class integrationTests: XCTestCase { newFactorKeys2.append(newFactorKey2) newFactorPubs2.append(newFactorPub2) - try await TssModule.delete_factor_pub(threshold_key: threshold, tss_tag: tag, factor_key: newFactorKeys[index].hex, auth_signatures: signatures, delete_factor_pub: newFactorPubs[index], nodeDetails: nodeDetail, torusUtils: torusUtils) + try await TssModule.delete_factor_pub(threshold_key: threshold, tss_tag: tag, factor_key: newFactorKeys[index].hex, delete_factor_pub: newFactorPubs[index]) } try await threshold.sync_local_metadata_transistions() print(try threshold.get_all_tss_tags()) diff --git a/Tests/tkeypkgTests/modules/tkey_pkgTssSecurityQuestionModuleTests.swift b/Tests/tkeypkgTests/modules/tkey_pkgTssSecurityQuestionModuleTests.swift new file mode 100644 index 0000000..f543625 --- /dev/null +++ b/Tests/tkeypkgTests/modules/tkey_pkgTssSecurityQuestionModuleTests.swift @@ -0,0 +1,224 @@ +import XCTest +import Foundation +@testable import tkey_pkg +import Foundation +import TorusUtils +import CommonSources +import FetchNodeDetails + +final class tkey_pkgTssSecurityQuestionModuleTests: XCTestCase { + private var threshold_key: ThresholdKey! + private var storage_layer: StorageLayer! + private var service_provider: ServiceProvider! + + override func setUp() async throws { + let TORUS_TEST_EMAIL = "saasa2123@tr.us" + let TORUS_TEST_VERIFIER = "torus-test-health" + + let nodeManager = NodeDetailManager(network: .sapphire(.SAPPHIRE_DEVNET)) + let nodeDetail = try await nodeManager.getNodeDetails(verifier: TORUS_TEST_VERIFIER, verifierID: TORUS_TEST_EMAIL) + let torusUtils = TorusUtils(serverTimeOffset: 1000, network: .sapphire(.SAPPHIRE_DEVNET)) + + let idToken = try generateIdToken(email: TORUS_TEST_EMAIL) + let verifierParams = VerifierParams(verifier_id: TORUS_TEST_EMAIL) + let retrievedShare = try await torusUtils.retrieveShares(endpoints: nodeDetail.torusNodeEndpoints, torusNodePubs: nodeDetail.torusNodePub, indexes: nodeDetail.torusIndexes, verifier: TORUS_TEST_VERIFIER, verifierParams: verifierParams, idToken: idToken) + let signature = retrievedShare.sessionData?.sessionTokenData + let signatures = signature!.compactMap { item in + item?.signature + } + + + let postbox_key = try! PrivateKey.generate() + let tssEndpoint0 = nodeDetail.torusNodeTSSEndpoints[0] + let metadataEndpoint = tssEndpoint0.replacingOccurrences(of: "/tss", with: "") + "/metadata" + let storage_layer_local = try! StorageLayer(enable_logging: true, host_url: metadataEndpoint, server_time_offset: 2) + let service_provider_local = try! ServiceProvider(enable_logging: true, postbox_key: postbox_key.hex, verifier: TORUS_TEST_VERIFIER, verifierId: TORUS_TEST_EMAIL, nodeDetails: nodeDetail) + let rss_comm = try! RssComm() + let threshold = try! ThresholdKey( + storage_layer: storage_layer_local, + service_provider: service_provider_local, + enable_logging: true, + manual_sync: false, + rss_comm: rss_comm + ) + + _ = try! await threshold.initialize() + + // setting variables needed for tss operations + threshold.setAuthSignatures(authSignatures: signatures) + threshold.setnodeDetails(nodeDetails: nodeDetail) + threshold.setTorusUtils(torusUtils: torusUtils) + + threshold_key = threshold + service_provider = service_provider_local + storage_layer = storage_layer_local + } + + override func tearDown() { + threshold_key = nil + } + + func test() async throws { + let _ = try! await threshold_key.reconstruct() + let question = "favorite marvel character" + let question2 = "favorite villian character" + let answer = "iron man" + let answer_2 = "captain america" + let factor_key = try! PrivateKey.generate() + let factor_pub = try factor_key.toPublic(format: .EllipticCompress) + var allIndex = try! threshold_key.get_shares_indexes() + allIndex.removeAll(where: {$0 == "1"}) + + try await TssModule.create_tagged_tss_share(threshold_key: threshold_key, tss_tag: "special", deviceTssShare: nil, factorPub: factor_pub, deviceTssIndex: 2) + + try! TssModule.backup_share_with_factor_key(threshold_key: threshold_key, shareIndex: allIndex[0], factorKey: factor_key.hex) + + let sq_factor = try await TssSecurityQuestionModule.set_security_question(threshold: threshold_key, question: question, answer: answer,factorKey: factor_key.hex, tag: "special") + + + do { + let _ = try await TssSecurityQuestionModule.set_security_question(threshold: threshold_key, question: question, answer: answer_2, factorKey: factor_key.hex, tag: "special") + XCTFail("Should not able to set quesetion twice") + } catch {} + + let questionReturn = try? TssSecurityQuestionModule.get_question(threshold: threshold_key, tag: "special") + XCTAssertEqual(questionReturn, question) + + let factor = try TssSecurityQuestionModule.recover_factor(threshold: threshold_key, answer: answer, tag: "special") + do { + let _ = try TssSecurityQuestionModule.recover_factor(threshold: threshold_key, answer: answer_2, tag: "special") + XCTFail("Should be able to get factor using incorrect answer") + } catch {} + try await threshold_key.input_factor_key(factorKey: factor) + // check for valid tss share + let _ = try await TssModule.get_tss_share(threshold_key: threshold_key, tss_tag: "special", factorKey: factor) + + XCTAssertEqual(String(factor.suffix(64)), sq_factor) + + // delete security question and add new security question + let factorPubDeleted = try await TssSecurityQuestionModule.delete_security_question(threshold: threshold_key, tag: "special", factorKey: factor_key.hex) + XCTAssertEqual(factorPubDeleted, try PrivateKey(hex: sq_factor).toPublic(format: .EllipticCompress)) + + + do { + let _ = try TssSecurityQuestionModule.get_question(threshold: threshold_key, tag: "special") + XCTFail("Should not able get question after delete") + }catch{} + do { + let _ = try TssSecurityQuestionModule.recover_factor(threshold: threshold_key, answer: answer, tag: "special") + XCTFail("Should not able get question after delete") + }catch{} + + // able to set new question and answer + let sq_factor2 = try await TssSecurityQuestionModule.set_security_question(threshold: threshold_key, question: question2, answer: answer_2, factorKey: factor_key.hex, tag: "special") + + let questionReturn2 = try TssSecurityQuestionModule.get_question(threshold: threshold_key, tag: "special") + XCTAssertEqual(questionReturn2, question2) + + let factor2 = try TssSecurityQuestionModule.recover_factor(threshold: threshold_key, answer: answer_2, tag: "special") + + do { + let _ = try TssSecurityQuestionModule.recover_factor(threshold: threshold_key, answer: answer, tag: "special") + XCTFail("Should be able to get factor using incorrect answer") + } catch {} + + XCTAssertEqual(String(factor2.suffix(64)), sq_factor2) + + // change answer and security question + do { + let _ = try await TssSecurityQuestionModule.change_security_question(threshold: threshold_key, newQuestion: question, newAnswer: answer, answer: answer, tag: "special") + XCTFail("Should be not able to change sq using incorrect answer") + } catch {} + + let (old_sq_factor, new_sq_factor) = try await TssSecurityQuestionModule.change_security_question(threshold: threshold_key, newQuestion: question, newAnswer: answer, answer: answer_2, tag: "special") + + XCTAssertEqual(old_sq_factor, sq_factor2) + + do { + let _ = try TssSecurityQuestionModule.recover_factor(threshold: threshold_key, answer: answer_2, tag: "special") + XCTFail("Should be not able to get factor using incorrect answer") + } catch {} + + let questionChanged = try TssSecurityQuestionModule.get_question(threshold: threshold_key, tag: "special") + let factorChanged = try TssSecurityQuestionModule.recover_factor(threshold: threshold_key, answer: answer, tag: "special") + + XCTAssertEqual(String(factorChanged.suffix(64)), new_sq_factor) + XCTAssertEqual(questionChanged, question) + + try await threshold_key.sync_local_metadata_transistions() + + let newThreshold = try! ThresholdKey( + storage_layer: storage_layer, + service_provider: service_provider, + enable_logging: true, + manual_sync: false + ) + + let _ = try await newThreshold.initialize(); + + let newInstanceQuestion = try TssSecurityQuestionModule.get_question(threshold: newThreshold, tag: "special") + let newInstanceFactor = try TssSecurityQuestionModule.recover_factor(threshold: newThreshold, answer: answer, tag: "special") + + try await newThreshold.input_factor_key(factorKey: newInstanceFactor) + try await newThreshold.input_factor_key(factorKey: newInstanceFactor) + let _ = try await newThreshold.reconstruct() + + XCTAssertEqual(String(newInstanceFactor.suffix(64)), new_sq_factor) + XCTAssertEqual(newInstanceQuestion, question) + } + + func test_js_compatible () async throws { + + let TORUS_TEST_EMAIL = "testing2001@example.com" + let TORUS_TEST_VERIFIER = "torus-test-health" + + let nodeManager = NodeDetailManager(network: .sapphire(.SAPPHIRE_DEVNET)) + let nodeDetail = try await nodeManager.getNodeDetails(verifier: TORUS_TEST_VERIFIER, verifierID: TORUS_TEST_EMAIL) + let torusUtils = TorusUtils(serverTimeOffset: 2, network: .sapphire(.SAPPHIRE_DEVNET)) + + let idToken = try generateIdToken(email: TORUS_TEST_EMAIL) + let verifierParams = VerifierParams(verifier_id: TORUS_TEST_EMAIL) + let retrievedShare = try await torusUtils.retrieveShares(endpoints: nodeDetail.torusNodeEndpoints, torusNodePubs: nodeDetail.torusNodePub, indexes: nodeDetail.torusIndexes, verifier: TORUS_TEST_VERIFIER, verifierParams: verifierParams, idToken: idToken) + let signature = retrievedShare.sessionData?.sessionTokenData + let signatures = signature!.compactMap { item in + item?.signature + } + + guard let postbox = retrievedShare.oAuthKeyData?.privKey else { + throw "invalid postbox key" + } + print(postbox) + let service_provider_local = try! ServiceProvider(enable_logging: true, postbox_key: postbox , verifier: TORUS_TEST_VERIFIER, verifierId: TORUS_TEST_EMAIL, nodeDetails: nodeDetail) + let rss_comm = try RssComm() + let threshold = try! ThresholdKey( + storage_layer: storage_layer, + service_provider: service_provider_local, + enable_logging: true, + manual_sync: false, + rss_comm: rss_comm + ) + + _ = try! await threshold.initialize() + + // setting variables needed for tss operations + threshold.setAuthSignatures(authSignatures: signatures) + threshold.setnodeDetails(nodeDetails: nodeDetail) + threshold.setTorusUtils(torusUtils: torusUtils) +// threshold.init + print( try threshold.get_key_details().pub_key.getPublicKey(format: .EllipticCompress)) + let factorKey = "36c1728c47c84dfe855949fa76daf82f8bda801af9374f30aa4c91b7fd7a8e3b" + let answer = "jsanswer" + let question = "js question" + + let questionResult = try TssSecurityQuestionModule.get_question(threshold: threshold, tag: "default") + print(questionResult) + let factor = try TssSecurityQuestionModule.recover_factor(threshold: threshold, answer: answer, tag: "default") + print(factor) + + try await threshold.input_factor_key(factorKey: factor) + try await threshold.reconstruct() + let (tssIndex, tssShare) = try await TssModule.get_tss_share(threshold_key: threshold, tss_tag: "default", factorKey: factor) + print (tssIndex) + print (tssShare) + } +}