From f98a143bbe4980a22975f1afa0e1e94bbe9895d8 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 1 May 2019 15:59:46 +1000 Subject: [PATCH 1/5] Added proof of work --- LokiKit/ProofOfWork.swift | 162 +++++++++++++++++++++++++++++++ Signal.xcodeproj/project.pbxproj | 4 + 2 files changed, 166 insertions(+) create mode 100644 LokiKit/ProofOfWork.swift diff --git a/LokiKit/ProofOfWork.swift b/LokiKit/ProofOfWork.swift new file mode 100644 index 000000000..9a31048da --- /dev/null +++ b/LokiKit/ProofOfWork.swift @@ -0,0 +1,162 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// +import CryptoSwift + +fileprivate extension Decimal { + /// Get the remainder of a Decimal + static func %(lhs: Decimal, rhs: Int) -> Decimal { + return Decimal(lhs.intValue % rhs) + } + + /// Divide a Decimal by an Int + static func /(lhs: Decimal, rhs: Int) -> Decimal { + return lhs / Decimal(rhs) + } + + /// Get the Int value of the decimal + var intValue: Int { + return Int(self.doubleValue) + } + + /// Get the Double value of the decimal + var doubleValue: Double { + return NSDecimalNumber(decimal: self).doubleValue + } + + /// Convert a Decimal to a UInt8 array of a given length + func toArray(ofLength length: Int) -> [UInt8] { + var array = [UInt8](repeating: 0, count: length) + + for i in 0..(lhs: [UInt8], rhs: [UInt8]) -> Bool { + guard lhs.count == rhs.count else { return false } + for i in 0.. rhs[i]) { return true } + if (lhs[i] < rhs[i]) { return false } + } + return false + } + + + /// Increment the UInt8 array by a given amount + /// + /// - Parameter amount: The amount to increment by + /// - Returns: The incrememnted array + func increment(by amount: Int) -> [UInt8] { + var newNonce = [UInt8](self) + var increment = amount + for i in (0.. 0 else { break } + let sum = Int(newNonce[i]) + increment + newNonce[i] = UInt8(sum % 256) + increment = sum / 256 + } + return newNonce + } +} + +/** + * The main logic which handles proof of work. + * + * This was copied from the messenger desktop. + * Ref: libloki/proof-of-work.js + */ +public enum ProofOfWork { + + static let nonceLength = 8 + + // Modify this value for difficulty scaling + enum NonceTrials { + static let development = 10 + static let production = 100 + } + + public struct Configuration { + var pubKey: String + var data: String + var timestamp: Date + var ttl: UInt + var isDevelopment = false + + func getPayload() -> [UInt8] { + let timestampString = String(timestamp.timeIntervalSince1970) + let ttlString = String(ttl) + let payloadString = timestampString + ttlString + pubKey + data + return [UInt8](payloadString.utf8) + } + } + + + /// Calculate a proof of work for the given configuration + /// + /// Ref: https://bitmessage.org/wiki/Proof_of_work + /// + /// - Parameter config: The configuration data + /// - Returns: A nonce string or nil if it failed + public static func calculate(with config: Configuration) -> String? { + let payload = config.getPayload() + let nonceTrials = config.isDevelopment ? NonceTrials.development : NonceTrials.production + let target = calcTarget(ttl: config.ttl, payloadLength: payload.count, nonceTrials: nonceTrials) + + // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER + let maxSafeInteger = pow(2, 53) - 1 + var trialValue = maxSafeInteger.toArray(ofLength: nonceLength) + + let initialHash = [UInt8](config.data.sha512().utf8) + var nonce = [UInt8](repeating: 0, count: nonceLength) + + while trialValue > target { + nonce = nonce.increment(by: 1) + + // This is different to the bitmessage pow + // resultHash = hash(nonce + hash(data)) ==> hash(nonce + initialHash) + let resultHash = (nonce + initialHash).sha512() + trialValue = Array(resultHash[0..<8]) + } + + return nonce.toBase64() + } + + /// Calculate the UInt8 target we need to reach + private static func calcTarget(ttl: UInt, payloadLength: Int, nonceTrials: Int) -> [UInt8] { + let decimalTTL = Decimal(ttl) + let decimalPayloadLength = Decimal(payloadLength) + let decimalNonceTrials = Decimal(nonceTrials) + + let decimalTwo16 = pow(2, 16) - 1 + let decimalTwo64 = pow(2, 64) - 1 + + // ttl converted to seconds + let ttlSeconds = decimalTTL / 1000 + + // Do all the calculations + let totalLength = decimalPayloadLength + Decimal(nonceLength) + let ttlMult = ttlSeconds * totalLength + let innerFrac = ttlMult / decimalTwo16 + let lenPlusInnerFrac = totalLength + innerFrac + let denominator = decimalNonceTrials * lenPlusInnerFrac + let targetNum = decimalTwo64 / denominator.intValue + + return targetNum.toArray(ofLength: nonceLength) + } +} diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index ae30a2082..d6a30a2eb 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 242D3F4B227966A30035F945 /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242D3F4A227966A20035F945 /* ProofOfWork.swift */; }; 2AE2882E4C2B96BFFF9EE27C /* Pods_SignalShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */; }; 3403B95D20EA9527001A1F44 /* OWSContactShareButtonsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */; }; 34074F61203D0CBE004596AE /* OWSSounds.m in Sources */ = {isa = PBXBuildFile; fileRef = 34074F5F203D0CBD004596AE /* OWSSounds.m */; }; @@ -659,6 +660,7 @@ 0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1C93CF3971B64E8B6C1F9AC1 /* Pods-SignalShareExtension.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.test.xcconfig"; sourceTree = ""; }; 1CE3CD5C23334683BDD3D78C /* Pods-Signal.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.test.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.test.xcconfig"; sourceTree = ""; }; + 242D3F4A227966A20035F945 /* ProofOfWork.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = ""; }; 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactShareButtonsView.m; sourceTree = ""; }; 3403B95C20EA9527001A1F44 /* OWSContactShareButtonsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactShareButtonsView.h; sourceTree = ""; }; @@ -2618,6 +2620,7 @@ B8DC3D7522795E2300D909D6 /* ECKeyPair.m */, B8DC3D7422795E2300D909D6 /* ECKeyPair.swift */, B8DC3D7622795E2400D909D6 /* LokiMessagingAPI.swift */, + 242D3F4A227966A20035F945 /* ProofOfWork.swift */, ); path = LokiKit; sourceTree = ""; @@ -3641,6 +3644,7 @@ 34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */, 4CEB78C92178EBAB00F315D2 /* OWSSessionResetJobRecord.m in Sources */, 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */, + 242D3F4B227966A30035F945 /* ProofOfWork.swift in Sources */, 340FC8BA204DAC8D007AEB0F /* FingerprintViewScanController.m in Sources */, 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */, 4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */, From d3f1ba5c8e660589b04d6eea7b87387b866579de Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 2 May 2019 10:09:20 +1000 Subject: [PATCH 2/5] Improved code --- LokiKit/ProofOfWork.swift | 50 +++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/LokiKit/ProofOfWork.swift b/LokiKit/ProofOfWork.swift index 9a31048da..20fc73a10 100644 --- a/LokiKit/ProofOfWork.swift +++ b/LokiKit/ProofOfWork.swift @@ -1,34 +1,32 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// import CryptoSwift -fileprivate extension Decimal { +private extension Int { + init(_ decimal: Decimal) { + let double = NSDecimalNumber(decimal: decimal).doubleValue + self.init(double) + } +} + +private extension UInt8 { + init(_ decimal: Decimal) { + self.init(Int(decimal)) + } +} + +private extension Decimal { /// Get the remainder of a Decimal static func %(lhs: Decimal, rhs: Int) -> Decimal { - return Decimal(lhs.intValue % rhs) + return Decimal(Int(lhs) % rhs) } /// Divide a Decimal by an Int static func /(lhs: Decimal, rhs: Int) -> Decimal { return lhs / Decimal(rhs) } - - /// Get the Int value of the decimal - var intValue: Int { - return Int(self.doubleValue) - } - - /// Get the Double value of the decimal - var doubleValue: Double { - return NSDecimalNumber(decimal: self).doubleValue - } /// Convert a Decimal to a UInt8 array of a given length func toArray(ofLength length: Int) -> [UInt8] { - var array = [UInt8](repeating: 0, count: length) - - for i in 0..(lhs: [UInt8], rhs: [UInt8]) -> Bool { guard lhs.count == rhs.count else { return false } - for i in 0.. rhs[i]) { return true } - if (lhs[i] < rhs[i]) { return false } - } - return false + + // lhs is greater than rhs if any value in lhs is greater than the corresponding value in the rhs + return zip(lhs, rhs).contains { $0 > $1 } } @@ -155,7 +149,7 @@ public enum ProofOfWork { let innerFrac = ttlMult / decimalTwo16 let lenPlusInnerFrac = totalLength + innerFrac let denominator = decimalNonceTrials * lenPlusInnerFrac - let targetNum = decimalTwo64 / denominator.intValue + let targetNum = decimalTwo64 / Int(denominator) return targetNum.toArray(ofLength: nonceLength) } From 1eed630af8ad682c0c95f65b0a3fd53f405b780a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 2 May 2019 12:40:42 +1000 Subject: [PATCH 3/5] Use UInt64 instead of Decimal when calculating proof of work. Fix target calculations in proof of work. Fix incorrect greater than comparison between UInt8 arrays. --- LokiKit/ProofOfWork.swift | 110 +++++++++++++++----------------------- 1 file changed, 43 insertions(+), 67 deletions(-) diff --git a/LokiKit/ProofOfWork.swift b/LokiKit/ProofOfWork.swift index 20fc73a10..368b0bebd 100644 --- a/LokiKit/ProofOfWork.swift +++ b/LokiKit/ProofOfWork.swift @@ -1,63 +1,40 @@ import CryptoSwift -private extension Int { +private extension UInt64 { init(_ decimal: Decimal) { - let double = NSDecimalNumber(decimal: decimal).doubleValue - self.init(double) + self.init(truncating: decimal as NSDecimalNumber) } } -private extension UInt8 { - init(_ decimal: Decimal) { - self.init(Int(decimal)) - } -} - -private extension Decimal { - /// Get the remainder of a Decimal - static func %(lhs: Decimal, rhs: Int) -> Decimal { - return Decimal(Int(lhs) % rhs) - } +// UInt8 Array specific stuff we need +private extension Array where Element == UInt8 { - /// Divide a Decimal by an Int - static func /(lhs: Decimal, rhs: Int) -> Decimal { - return lhs / Decimal(rhs) - } - - /// Convert a Decimal to a UInt8 array of a given length - func toArray(ofLength length: Int) -> [UInt8] { - return (0..> $0 & 0x000000FF) } + self.init(array) } -} - -// UInt8 Array specific stuff we need -private extension Array where Element == UInt8 { /// Compare if lhs array is greater than rhs array static func >(lhs: [UInt8], rhs: [UInt8]) -> Bool { guard lhs.count == rhs.count else { return false } - // lhs is greater than rhs if any value in lhs is greater than the corresponding value in the rhs - return zip(lhs, rhs).contains { $0 > $1 } + for i in (0.. rhs[i] + } + return false } - /// Increment the UInt8 array by a given amount /// /// - Parameter amount: The amount to increment by /// - Returns: The incrememnted array func increment(by amount: Int) -> [UInt8] { - var newNonce = [UInt8](self) + var newNonce = self var increment = amount for i in (0.. 0 else { break } @@ -77,26 +54,27 @@ private extension Array where Element == UInt8 { */ public enum ProofOfWork { - static let nonceLength = 8 + // If this changes then we also have to use something other than UInt64 to support the new length + private static let nonceLength = 8 // Modify this value for difficulty scaling - enum NonceTrials { + private enum NonceTrials { static let development = 10 static let production = 100 } - public struct Configuration { + struct Configuration { var pubKey: String var data: String var timestamp: Date - var ttl: UInt + var ttl: Int var isDevelopment = false - func getPayload() -> [UInt8] { - let timestampString = String(timestamp.timeIntervalSince1970) + var payload: [UInt8] { + let timestampString = String(Int(timestamp.timeIntervalSince1970)) let ttlString = String(ttl) let payloadString = timestampString + ttlString + pubKey + data - return [UInt8](payloadString.utf8) + return payloadString.bytes } } @@ -107,16 +85,16 @@ public enum ProofOfWork { /// /// - Parameter config: The configuration data /// - Returns: A nonce string or nil if it failed - public static func calculate(with config: Configuration) -> String? { - let payload = config.getPayload() + static func calculate(with config: Configuration) -> String? { + let payload = config.payload let nonceTrials = config.isDevelopment ? NonceTrials.development : NonceTrials.production let target = calcTarget(ttl: config.ttl, payloadLength: payload.count, nonceTrials: nonceTrials) // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER - let maxSafeInteger = pow(2, 53) - 1 - var trialValue = maxSafeInteger.toArray(ofLength: nonceLength) + let maxSafeInteger = UInt64(pow(2, 53) - 1) + var trialValue = [UInt8](maxSafeInteger) - let initialHash = [UInt8](config.data.sha512().utf8) + let initialHash = payload.sha512() var nonce = [UInt8](repeating: 0, count: nonceLength) while trialValue > target { @@ -132,25 +110,23 @@ public enum ProofOfWork { } /// Calculate the UInt8 target we need to reach - private static func calcTarget(ttl: UInt, payloadLength: Int, nonceTrials: Int) -> [UInt8] { - let decimalTTL = Decimal(ttl) - let decimalPayloadLength = Decimal(payloadLength) - let decimalNonceTrials = Decimal(nonceTrials) - - let decimalTwo16 = pow(2, 16) - 1 - let decimalTwo64 = pow(2, 64) - 1 - + private static func calcTarget(ttl: Int, payloadLength: Int, nonceTrials: Int) -> [UInt8] { + let two16 = UInt64(pow(2, 16) - 1) + let two64 = UInt64(pow(2, 64) - 1) + // ttl converted to seconds - let ttlSeconds = decimalTTL / 1000 - + let ttlSeconds = ttl / 1000 + // Do all the calculations - let totalLength = decimalPayloadLength + Decimal(nonceLength) - let ttlMult = ttlSeconds * totalLength - let innerFrac = ttlMult / decimalTwo16 + let totalLength = UInt64(payloadLength + nonceLength) + let ttlMult = UInt64(ttlSeconds) * totalLength + + // UInt64 values + let innerFrac = ttlMult / two16 let lenPlusInnerFrac = totalLength + innerFrac - let denominator = decimalNonceTrials * lenPlusInnerFrac - let targetNum = decimalTwo64 / Int(denominator) + let denominator = UInt64(nonceTrials) * lenPlusInnerFrac + let targetNum = two64 / denominator - return targetNum.toArray(ofLength: nonceLength) + return [UInt8](targetNum) } } From 0f33085de47a300ab04624389e3aded97d7d16e2 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 2 May 2019 14:54:14 +1000 Subject: [PATCH 4/5] Set trialValue to maximum --- LokiKit/ProofOfWork.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/LokiKit/ProofOfWork.swift b/LokiKit/ProofOfWork.swift index 368b0bebd..34295573a 100644 --- a/LokiKit/ProofOfWork.swift +++ b/LokiKit/ProofOfWork.swift @@ -9,7 +9,7 @@ private extension UInt64 { // UInt8 Array specific stuff we need private extension Array where Element == UInt8 { - // Convert a UInt64 into an array + // Convert a UInt64 into an array of size 8 init(_ uint64: UInt64) { let array = stride(from: 0, to: 64, by: 8).reversed().map { UInt8(uint64 >> $0 & 0x000000FF) @@ -90,9 +90,8 @@ public enum ProofOfWork { let nonceTrials = config.isDevelopment ? NonceTrials.development : NonceTrials.production let target = calcTarget(ttl: config.ttl, payloadLength: payload.count, nonceTrials: nonceTrials) - // Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER - let maxSafeInteger = UInt64(pow(2, 53) - 1) - var trialValue = [UInt8](maxSafeInteger) + // Start with most the max value we can + var trialValue = [UInt8](repeating: UInt8.max, count: nonceLength) let initialHash = payload.sha512() var nonce = [UInt8](repeating: 0, count: nonceLength) From 425038dbfdbaf7653e68d5909670af6d441b7238 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 2 May 2019 15:06:46 +1000 Subject: [PATCH 5/5] Point to updated pods commit. --- Pods | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pods b/Pods index aed207f02..34ae9f4f2 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit aed207f026696cbb76066748fba6689a9c04194e +Subproject commit 34ae9f4f2dd47c4d66de7525fbe1054f4f573512