-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: tss security question #10
Open
ieow
wants to merge
17
commits into
alpha
Choose a base branch
from
feat/tssSecurityQuestion
base: alpha
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
ec4b442
feat: tss security question
ieow 9ea5517
fix: test
ieow 05eb479
fix: set_store_domain bug
ieow 97af06f
fix: add test for delete question
ieow 515c59f
feat: update with factorPub
ieow 8640242
Merge branch 'alpha' into feat/tssSecurityQuestion
ieow afd10e6
Merge pull request #13 from tkey/alpha
himanshuchawla009 69ad165
feat: update sq using encryption
ieow 1a3ff7d
fix: docs
ieow b373222
fix: use hash as privatekey
ieow eacaa85
feat: move signatures, torusUtils, nodeDetails
ieow e5855d2
Merge branch 'feat/move-signatures-to-threshold' into feat/tssSecurit…
ieow 302ff15
fix: use hash as factorkey
ieow 435e59e
fix: ts compatible + security-question recover
ieow 726ff6d
fix: support encType
ieow 355d8a4
fix: authmetadata signature verify
ieow 57c20a5
feat: add patch input factor key
ieow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Int8>(mutating: (curveN as NSString).utf8String) | ||
let keyPointer = UnsafeMutablePointer<Int8>(mutating: (key as NSString).utf8String) | ||
let msgPointer = UnsafeMutablePointer<Int8>(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<Int8>(mutating: (key as NSString).utf8String) | ||
let msgPointer = UnsafeMutablePointer<Int8>(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 | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
228 changes: 228 additions & 0 deletions
228
Sources/ThresholdKey/Modules/TssSecurityQuestionModule.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this domain key same in web? |
||
|
||
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we directly exposing the encrypt and decrypt methods here?
All the logic around this and setting the relevant general store domain should be handled internally, no?
i.e How would we be able to tell that the library is behaving the same way in all situations? Should this not be implemented as a module alongside the security_question module in the library?