diff --git a/Package.resolved b/Package.resolved index 99bb4888..ba3414a2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -36,6 +36,15 @@ "version" : "4.13.2" } }, + { + "identity" : "sentry-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/getsentry/sentry-cocoa", + "state" : { + "revision" : "5575af93efb776414f243e93d6af9f6258dc539a", + "version" : "8.36.0" + } + }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 25e8de38..f17bde74 100644 --- a/Package.swift +++ b/Package.swift @@ -15,6 +15,7 @@ let package = Package( .package(url: "https://github.com/torusresearch/fetch-node-details-swift", from: "6.0.3"), // NB: jwt-kit may only be a dependency in tests or it will break cocoapod support .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"), + .package(url: "https://github.com/getsentry/sentry-cocoa", from: "8.36.0"), ], targets: [ .target( @@ -22,6 +23,7 @@ let package = Package( dependencies: [ .product(name: "FetchNodeDetails", package: "fetch-node-details-swift"), .product(name: "curveSecp256k1", package: "curvelib.swift"), + .product(name: "Sentry", package: "sentry-cocoa"), ]), .testTarget( name: "TorusUtilsTests", diff --git a/Sources/TorusUtils/Helpers/Common.swift b/Sources/TorusUtils/Helpers/Common.swift index e62140c4..470d08b3 100644 --- a/Sources/TorusUtils/Helpers/Common.swift +++ b/Sources/TorusUtils/Helpers/Common.swift @@ -54,7 +54,9 @@ internal func thresholdSame(arr: [T], threshold: Int) throws -> T? let jsonEncoder = JSONEncoder() jsonEncoder.outputFormatting = .sortedKeys for (_, value) in arr.enumerated() { - guard let jsonString = String(data: try jsonEncoder.encode(value), encoding: .utf8) else { throw TorusUtilError.encodingFailed("thresholdSame") + guard let jsonString = String(data: try jsonEncoder.encode(value), encoding: .utf8) else { + SentryUtils.captureException("thresholdSame") + throw TorusUtilError.encodingFailed("thresholdSame") } if let _ = hashmap[jsonString] { hashmap[jsonString]! += 1 diff --git a/Sources/TorusUtils/Helpers/KeyUtils.swift b/Sources/TorusUtils/Helpers/KeyUtils.swift index 006ab0c9..eb65ce76 100644 --- a/Sources/TorusUtils/Helpers/KeyUtils.swift +++ b/Sources/TorusUtils/Helpers/KeyUtils.swift @@ -10,7 +10,10 @@ enum TorusKeyType: String, Equatable, Hashable, Codable { public class KeyUtils { public static func keccak256Data(_ input: String) throws -> String { - guard let data = input.data(using: .utf8) else { throw TorusUtilError.invalidInput } + guard let data = input.data(using: .utf8) else { + SentryUtils.captureException("\(TorusUtilError.invalidInput)") + throw TorusUtilError.invalidInput + } return try keccak256(data: data).toHexString() } @@ -68,6 +71,7 @@ public class KeyUtils { if (publicKeyUnprefixed.count <= 128) { publicKeyUnprefixed = publicKeyUnprefixed.addLeading0sForLength128() } else { + SentryUtils.captureException("\(TorusUtilError.invalidPubKeySize)") throw TorusUtilError.invalidPubKeySize } @@ -148,6 +152,7 @@ public class KeyUtils { internal static func generateShares(keyType: TorusKeyType = .secp256k1, serverTimeOffset: Int, nodeIndexes: [BigUInt], nodePubKeys: [INodePub], privateKey: String) throws -> [ImportedShare] { if keyType != TorusKeyType.secp256k1 { + SentryUtils.captureException("Unsupported key type") throw TorusUtilError.runtime("Unsupported key type") } diff --git a/Sources/TorusUtils/Helpers/LangrangeInterpolatePoly.swift b/Sources/TorusUtils/Helpers/LangrangeInterpolatePoly.swift index 62df8e57..11f47762 100644 --- a/Sources/TorusUtils/Helpers/LangrangeInterpolatePoly.swift +++ b/Sources/TorusUtils/Helpers/LangrangeInterpolatePoly.swift @@ -89,6 +89,7 @@ internal class Lagrange { let indexList: [BigInt] = nodeIndex.map({ BigInt($0) }) if sharesList.count != indexList.count { + SentryUtils.captureException("sharesList not equal to indexList length in lagrangeInterpolation") throw TorusUtilError.runtime("sharesList not equal to indexList length in lagrangeInterpolation") } @@ -112,6 +113,7 @@ internal class Lagrange { guard let inv = lower.inverse(KeyUtils.getOrderOfCurve()) else { + SentryUtils.captureException("\(TorusUtilError.decryptionFailed)") throw TorusUtilError.decryptionFailed } var delta = (upper * inv).modulus(KeyUtils.getOrderOfCurve()) @@ -121,6 +123,7 @@ internal class Lagrange { } if secret == BigUInt(0) { + SentryUtils.captureException("\(TorusUtilError.interpolationFailed)") throw TorusUtilError.interpolationFailed } @@ -128,6 +131,7 @@ internal class Lagrange { if sharesDecrypt == sharesList.count { return secretString } else { + SentryUtils.captureException("\(TorusUtilError.interpolationFailed)") throw TorusUtilError.interpolationFailed } } diff --git a/Sources/TorusUtils/Helpers/MetadataUtils.swift b/Sources/TorusUtils/Helpers/MetadataUtils.swift index ecb70c0c..43cbd4a0 100644 --- a/Sources/TorusUtils/Helpers/MetadataUtils.swift +++ b/Sources/TorusUtils/Helpers/MetadataUtils.swift @@ -40,6 +40,7 @@ internal class MetadataUtils { guard let url = URL(string: url) else { + SentryUtils.captureException("Invalid Url \(url)") throw TorusUtilError.runtime("Invalid Url \(url)") } var rq = URLRequest(url: url) @@ -103,6 +104,7 @@ internal class MetadataUtils { if case .sapphire = network { return try await getOrSetNonce(legacyMetadataHost: metadataHost, serverTimeOffset: serverTimeOffset ?? Int(trunc(Double(0 + Int(Date().timeIntervalSince1970)))), X: X, Y: Y, privateKey: privateKey, getOnly: getOnly, keyType: keyType) } else { + SentryUtils.captureException("\(TorusUtilError.metadataNonceMissing)") throw TorusUtilError.metadataNonceMissing } } diff --git a/Sources/TorusUtils/Helpers/NodeUtils.swift b/Sources/TorusUtils/Helpers/NodeUtils.swift index 74b90c8c..84b6e2d1 100644 --- a/Sources/TorusUtils/Helpers/NodeUtils.swift +++ b/Sources/TorusUtils/Helpers/NodeUtils.swift @@ -160,6 +160,7 @@ internal class NodeUtils { var importedShareCount = 0 if importedShares != nil && importedShares!.count > 0 { if importedShares!.count != endpoints.count { + SentryUtils.captureException("\(TorusUtilError.importShareFailed)") throw TorusUtilError.importShareFailed } isImportShareReq = true @@ -214,6 +215,7 @@ internal class NodeUtils { } if importedShareCount > 0 && !(nodeSigs.count == endpoints.count) { + SentryUtils.captureException("\(TorusUtilError.commitmentRequestFailed)") throw TorusUtilError.commitmentRequestFailed } @@ -286,6 +288,7 @@ internal class NodeUtils { } if isImportShareReq && !shareImportSuccess { + SentryUtils.captureException("\(TorusUtilError.importShareFailed)") throw TorusUtilError.importShareFailed } @@ -364,6 +367,7 @@ internal class NodeUtils { } if thresholdPublicKey == nil { + SentryUtils.captureException("\(TorusUtilError.retrieveOrImportShareError)") throw TorusUtilError.retrieveOrImportShareError } @@ -395,6 +399,7 @@ internal class NodeUtils { // Invert comparision to return error early if !(shareResponses.count >= thresholdReqCount && thresholdPublicKey != nil && (thresholdNonceData != nil || verifierParams.extended_verifier_id != nil || TorusUtils.isLegacyNetworkRouteMap(network: network))) { + SentryUtils.captureException("\(TorusUtilError.retrieveOrImportShareError)") throw TorusUtilError.retrieveOrImportShareError } @@ -434,9 +439,11 @@ internal class NodeUtils { let latestKey = item.keys[0] nodeIndexes.append(latestKey.nodeIndex) guard let cipherData = Data(base64Encoded: latestKey.share) else { + SentryUtils.captureException("cipher is not base64 encoded") throw TorusUtilError.decodingFailed("cipher is not base64 encoded") } guard let cipherTextHex = String(data: cipherData, encoding: .utf8) else { + SentryUtils.captureException("cipher is not base64 encoded") throw TorusUtilError.decodingFailed("cipherData is not utf8") } let decrypted = try MetadataUtils.decryptNodeData(eciesData: latestKey.shareMetadata, ciphertextHex: cipherTextHex, privKey: sessionAuthKeySerialized) @@ -450,12 +457,14 @@ internal class NodeUtils { let validSigs = sessionTokenSigs.filter({ $0 != nil }).map({ $0! }) if verifierParams.extended_verifier_id == nil && validSigs.count < threshold { + SentryUtils.captureException("\(TorusUtilError.retrieveOrImportShareError)") throw TorusUtilError.retrieveOrImportShareError } let validTokens = sessionTokens.filter({ $0 != nil }).map({ $0! }) if verifierParams.extended_verifier_id == nil && validTokens.count < threshold { + SentryUtils.captureException("Insufficient number of signatures from nodes") throw TorusUtilError.runtime("Insufficient number of signatures from nodes") } @@ -503,6 +512,7 @@ internal class NodeUtils { } if privateKey == nil { + SentryUtils.captureException("\(TorusUtilError.privateKeyDeriveFailed)") throw TorusUtilError.privateKeyDeriveFailed } @@ -555,11 +565,13 @@ internal class NodeUtils { finalPubKey = try KeyUtils.combinePublicKeys(keys: [oAuthPubKey, publicNonce]) pubNonce = PubNonce(x: thresholdNonceData!.pubNonce!.x, y: thresholdNonceData!.pubNonce!.y) } else { + SentryUtils.captureException("\(TorusUtilError.pubNonceMissing)") throw TorusUtilError.pubNonceMissing } } if finalPubKey == nil { + SentryUtils.captureException("\(TorusUtilError.retrieveOrImportShareError)") throw TorusUtilError.retrieveOrImportShareError } @@ -577,8 +589,10 @@ internal class NodeUtils { // This is a sanity check to make doubly sure we are returning the correct private key after importing a share if isImportShareReq { if newPrivateKey == nil { + SentryUtils.captureException("\(TorusUtilError.importShareFailed)") throw TorusUtilError.importShareFailed } else if (!(finalPrivKey == newPrivateKey!.addLeading0sForLength64())) { + SentryUtils.captureException("\(TorusUtilError.importShareFailed)") throw TorusUtilError.importShareFailed } } @@ -587,7 +601,9 @@ internal class NodeUtils { if typeOfUser == .v2 { isUpgraded = metadataNonce == BigInt(0) } - + + SentryUtils.logInformation(clientId: clientId, finalEvmAddress: finalEvmAddress, platform: "torus-utils-swift") + return TorusKey( finalKeyData: TorusKey.FinalKeyData( evmAddress: finalEvmAddress, diff --git a/Sources/TorusUtils/Point.swift b/Sources/TorusUtils/Point.swift index 942f2e84..cd5b8373 100644 --- a/Sources/TorusUtils/Point.swift +++ b/Sources/TorusUtils/Point.swift @@ -18,12 +18,14 @@ internal struct Point: Codable { if let xCoord = BigInt(x, radix: 16) { self.x = xCoord } else { + SentryUtils.captureException("\(TorusUtilError.invalidInput)") throw TorusUtilError.invalidInput } if let yCoord = BigInt(y, radix: 16) { self.y = yCoord } else { + SentryUtils.captureException("\(TorusUtilError.invalidInput)") throw TorusUtilError.invalidInput } } diff --git a/Sources/TorusUtils/Share.swift b/Sources/TorusUtils/Share.swift index 1a96d63b..a5b6bd9d 100644 --- a/Sources/TorusUtils/Share.swift +++ b/Sources/TorusUtils/Share.swift @@ -9,12 +9,14 @@ internal class Share: Codable { if let si = BigInt(shareIndex, radix: 16) { self.shareIndex = si } else { + SentryUtils.captureException("\(TorusUtilError.invalidInput)") throw TorusUtilError.invalidInput } if let s = BigInt(share, radix: 16) { self.share = s } else { + SentryUtils.captureException("\(TorusUtilError.invalidInput)") throw TorusUtilError.invalidInput } } diff --git a/Sources/TorusUtils/TorusUtils.swift b/Sources/TorusUtils/TorusUtils.swift index e77afc12..f4a9e3eb 100644 --- a/Sources/TorusUtils/TorusUtils.swift +++ b/Sources/TorusUtils/TorusUtils.swift @@ -2,6 +2,7 @@ import BigInt import FetchNodeDetails import Foundation import OSLog +import Sentry #if canImport(curveSecp256k1) import curveSecp256k1 #endif @@ -37,6 +38,7 @@ public class TorusUtils { /// /// - Throws: `TorusUtilError.invalidInput` public init(params: TorusOptions, loglevel: OSLogType = .default) throws { + SentryUtils.initSentry() var defaultHost = "" if params.legacyMetadataHost == nil { if case let .legacy(urlHost) = params.network { @@ -50,6 +52,7 @@ public class TorusUtils { defaultHost = "https://node-1.dev-node.web3auth.io/metadata" } } else { + SentryUtils.captureException("\(TorusUtilError.invalidInput)") throw TorusUtilError.invalidInput } } @@ -176,6 +179,7 @@ public class TorusUtils { ) async throws -> TorusKey { let nodePubs = TorusNodePubModelToINodePub(nodes: nodePubKeys) if endpoints.count != nodeIndexes.count { + SentryUtils.captureException("Length of endpoints must be the same as length of nodeIndexes") throw TorusUtilError.runtime("Length of endpoints must be the same as length of nodeIndexes") } @@ -214,17 +218,21 @@ public class TorusUtils { if keyAssignResult.errorResult != nil { let error = keyAssignResult.errorResult!.message if error.lowercased().contains("verifier not supported") { + SentryUtils.captureException("Verifier not supported. Check if you: 1. Are on the right network (Torus testnet/mainnet) 2. Have setup a verifier on dashboard.web3auth.io?") throw TorusUtilError.runtime("Verifier not supported. Check if you: 1. Are on the right network (Torus testnet/mainnet) 2. Have setup a verifier on dashboard.web3auth.io?") } else { + SentryUtils.captureException(error) throw TorusUtilError.runtime(error) } } if keyAssignResult.keyResult == nil || keyAssignResult.keyResult?.keys.count == 0 { + SentryUtils.captureException("node results do not match at final lookup") throw TorusUtilError.runtime("node results do not match at final lookup") } if keyAssignResult.nonceResult == nil && extendedVerifierId == nil && !TorusUtils.isLegacyNetworkRouteMap(network: network) { + SentryUtils.captureException("metadata nonce is missing in share response") throw TorusUtilError.runtime("metadata nonce is missing in share response") } @@ -257,11 +265,13 @@ public class TorusUtils { finalPubKey = try KeyUtils.combinePublicKeys(keys: [oAuthPubKey!, pubNonceKey]) pubNonce = pubNonceResult } else { + SentryUtils.captureException("\(TorusUtilError.pubNonceMissing)") throw TorusUtilError.pubNonceMissing } } if oAuthPubKey == nil || finalPubKey == nil { + SentryUtils.captureException("\(TorusUtilError.privateKeyDeriveFailed)") throw TorusUtilError.privateKeyDeriveFailed } @@ -329,6 +339,7 @@ public class TorusUtils { finalPubKey = try KeyUtils.combinePublicKeys(keys: [oAuthPubKey, pubNonceKey]) pubNonce = nonceResult!.pubNonce! } else { + SentryUtils.captureException("\(TorusUtilError.metadataNonceMissing)") throw TorusUtilError.metadataNonceMissing } } else { @@ -346,6 +357,7 @@ public class TorusUtils { let oAuthAddress = try KeyUtils.generateAddressFromPubKey(publicKeyX: X, publicKeyY: Y) if typeOfUser == .v2 && finalPubKey == nil { + SentryUtils.captureException("\(TorusUtilError.privateKeyDeriveFailed)") throw TorusUtilError.privateKeyDeriveFailed } diff --git a/Sources/TorusUtils/analytics/SentryUtils.swift b/Sources/TorusUtils/analytics/SentryUtils.swift new file mode 100644 index 00000000..c8455f48 --- /dev/null +++ b/Sources/TorusUtils/analytics/SentryUtils.swift @@ -0,0 +1,53 @@ +// +// File.swift +// +// +// Created by Gaurav Goel on 16/09/24. +// + +import Sentry + +class SentryUtils { + + static func initSentry() { + SentrySDK.start { options in + options.dsn = "https://9ad72d7939d850442daad4873196a4eb@o503538.ingest.us.sentry.io/4507961375195136" + options.debug = true + options.tracesSampleRate = 1.0 + } + } + + + static func captureException(_ message: String) { + let error = NSError(domain: "torus-utils-swift", code: 0, userInfo: [NSLocalizedDescriptionKey: message]) + SentrySDK.capture(error: error) + } + + + static func addBreadcrumb(message: String) { + let breadcrumb = Breadcrumb() + breadcrumb.message = message + breadcrumb.category = "custom" + SentrySDK.addBreadcrumb(breadcrumb) + } + + // Static method to log information by setting tags + static func logInformation(clientId: String, finalEvmAddress: String, platform: String) { + SentrySDK.configureScope { scope in + scope.setTag(value: clientId, key: "clientId") + scope.setTag(value: finalEvmAddress, key: "finalEvmAddress") + scope.setTag(value: platform, key: "platform") + } + } + + static func setContext(key: String, value: String) { + SentrySDK.configureScope { scope in + scope.setExtra(value: value, key: key) + } + } + + static func close() { + SentrySDK.close() + } +} +