diff --git a/Example/FearlessUtils.xcodeproj/project.pbxproj b/Example/FearlessUtils.xcodeproj/project.pbxproj index c1e681b..f356302 100644 --- a/Example/FearlessUtils.xcodeproj/project.pbxproj +++ b/Example/FearlessUtils.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; + 845D80ED24C8D91A00EC2540 /* SeedFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845D80EC24C8D91A00EC2540 /* SeedFactoryTests.swift */; }; 84DA3B3C24C8CE5900B5E27F /* ScaleUInt32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DA3B3224C8CE5900B5E27F /* ScaleUInt32Tests.swift */; }; 84DA3B3D24C8CE5900B5E27F /* ScaleCompactIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DA3B3324C8CE5900B5E27F /* ScaleCompactIntTests.swift */; }; 84DA3B3E24C8CE5900B5E27F /* ScaleInt64Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DA3B3424C8CE5900B5E27F /* ScaleInt64Tests.swift */; }; @@ -48,6 +49,7 @@ 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 607FACE51AFB9204008FA782 /* FearlessUtils_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FearlessUtils_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7BCE5EB68B0A48835979E64A /* Pods-FearlessUtils_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FearlessUtils_Tests.debug.xcconfig"; path = "Target Support Files/Pods-FearlessUtils_Tests/Pods-FearlessUtils_Tests.debug.xcconfig"; sourceTree = ""; }; + 845D80EC24C8D91A00EC2540 /* SeedFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedFactoryTests.swift; sourceTree = ""; }; 84DA3B3224C8CE5900B5E27F /* ScaleUInt32Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleUInt32Tests.swift; sourceTree = ""; }; 84DA3B3324C8CE5900B5E27F /* ScaleCompactIntTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleCompactIntTests.swift; sourceTree = ""; }; 84DA3B3424C8CE5900B5E27F /* ScaleInt64Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleInt64Tests.swift; sourceTree = ""; }; @@ -140,9 +142,18 @@ name = "Podspec Metadata"; sourceTree = ""; }; + 845D80EB24C8D8F500EC2540 /* Common */ = { + isa = PBXGroup; + children = ( + 845D80EC24C8D91A00EC2540 /* SeedFactoryTests.swift */, + ); + path = Common; + sourceTree = ""; + }; 84DA3B3024C8CE5900B5E27F /* Tests */ = { isa = PBXGroup; children = ( + 845D80EB24C8D8F500EC2540 /* Common */, 84DA3B4824C8CEEF00B5E27F /* Info.plist */, 84DA3B3124C8CE5900B5E27F /* Scale */, ); @@ -300,12 +311,14 @@ "${BUILT_PRODUCTS_DIR}/BigInt/BigInt.framework", "${BUILT_PRODUCTS_DIR}/FearlessUtils/FearlessUtils.framework", "${BUILT_PRODUCTS_DIR}/IrohaCrypto/IrohaCrypto.framework", + "${BUILT_PRODUCTS_DIR}/xxHash-Swift/xxHash_Swift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BigInt.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FearlessUtils.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IrohaCrypto.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/xxHash_Swift.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -380,6 +393,7 @@ 84DA3B4324C8CE5A00B5E27F /* ScaleStringTests.swift in Sources */, 84DA3B4224C8CE5A00B5E27F /* ScaleUInt64Tests.swift in Sources */, 84DA3B4124C8CE5A00B5E27F /* ScaleInt32Tests.swift in Sources */, + 845D80ED24C8D91A00EC2540 /* SeedFactoryTests.swift in Sources */, 84DA3B3C24C8CE5900B5E27F /* ScaleUInt32Tests.swift in Sources */, 84DA3B3F24C8CE5900B5E27F /* ScaleUInt16Tests.swift in Sources */, ); diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 3ce2c1c..32c5f3d 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -3,6 +3,7 @@ PODS: - FearlessUtils (0.1.0): - BigInt (~> 5.0) - IrohaCrypto/sr25519 (~> 0.4.0) + - xxHash-Swift (~> 1.0.0) - IrohaCrypto/BIP39 (0.4.3): - IrohaCrypto/Common - IrohaCrypto/blake2 (0.4.3) @@ -11,6 +12,7 @@ PODS: - IrohaCrypto/BIP39 - IrohaCrypto/blake2 - IrohaCrypto/Common + - xxHash-Swift (1.0.13) DEPENDENCIES: - FearlessUtils (from `../`) @@ -19,6 +21,7 @@ SPEC REPOS: trunk: - BigInt - IrohaCrypto + - xxHash-Swift EXTERNAL SOURCES: FearlessUtils: @@ -26,8 +29,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: BigInt: 74b4d88367b0e819d9f77393549226d36faeb0d8 - FearlessUtils: 58a574cf727db20bde817672506d4cc38a1e70d7 + FearlessUtils: aef3e364c3767287e5302cedff888d3e13dddcdd IrohaCrypto: cfdfe162065705d1055bed4e37345abcbf5184de + xxHash-Swift: 30bd6a7507b3b7348a277c49b1cb6346c2905ec7 PODFILE CHECKSUM: 002fb4b06641c82f822cb8ddc9d08ba3f79755d6 diff --git a/FearlessUtils.podspec b/FearlessUtils.podspec index 4c13b40..c389e5d 100644 --- a/FearlessUtils.podspec +++ b/FearlessUtils.podspec @@ -21,6 +21,7 @@ Pod::Spec.new do |s| s.source_files = 'FearlessUtils/Classes/**/*' s.dependency 'IrohaCrypto/sr25519', '~> 0.4.0' s.dependency 'BigInt', '~> 5.0' + s.dependency 'xxHash-Swift', '~> 1.0.0' s.test_spec do |ts| ts.source_files = 'Tests/**/*.swift' diff --git a/FearlessUtils/Classes/Common/Data+Hash.swift b/FearlessUtils/Classes/Common/Data+Hash.swift new file mode 100644 index 0000000..a48d821 --- /dev/null +++ b/FearlessUtils/Classes/Common/Data+Hash.swift @@ -0,0 +1,25 @@ +import Foundation +import xxHash_Swift +import IrohaCrypto + +public extension Data { + func xxh128() -> Data { + var hash1Value = XXH64.digest(self, seed: 0) + let hash1 = Data(bytes: &hash1Value, count: MemoryLayout.size) + + var hash2Value = XXH64.digest(self, seed: 1) + let hash2 = Data(bytes: &hash2Value, count: MemoryLayout.size) + + return hash1 + hash2 + } + + func blake128Concat() throws -> Data { + let hashed = try (self as NSData).blake2b(16) + return hashed + self + } + + func twox64Concat() -> Data { + var hash1Value = XXH64.digest(self, seed: 0) + return Data(bytes: &hash1Value, count: MemoryLayout.size) + self + } +} diff --git a/FearlessUtils/Classes/Common/SeedFactory.swift b/FearlessUtils/Classes/Common/SeedFactory.swift new file mode 100644 index 0000000..2571931 --- /dev/null +++ b/FearlessUtils/Classes/Common/SeedFactory.swift @@ -0,0 +1,36 @@ +import Foundation +import IrohaCrypto + +public typealias SeedFactoryResult = (seed: Data, mnemonic: IRMnemonicProtocol) + +public protocol SeedFactoryProtocol { + func createSeed(from password: String, strength: IRMnemonicStrength) throws -> SeedFactoryResult + func deriveSeed(from mnemonicWords: String, password: String) throws -> SeedFactoryResult +} + +public struct SeedFactory: SeedFactoryProtocol { + private static let seedLength: Int = 32 + + private let seedFactory: SNBIP39SeedCreatorProtocol = SNBIP39SeedCreator() + private let mnemonicCreator: IRMnemonicCreatorProtocol + + public init(mnemonicLanguage: IRMnemonicLanguage = .english) { + mnemonicCreator = IRMnemonicCreator(language: mnemonicLanguage) + } + + public func createSeed(from password: String, + strength: IRMnemonicStrength) throws -> SeedFactoryResult { + let mnemonic = try mnemonicCreator.randomMnemonic(strength) + let seed = try seedFactory.deriveSeed(from: mnemonic.entropy(), passphrase: password) + + return SeedFactoryResult(seed: seed.prefix(Self.seedLength), mnemonic: mnemonic) + } + + public func deriveSeed(from mnemonicWords: String, + password: String) throws -> SeedFactoryResult { + let mnemonic = try mnemonicCreator.mnemonic(fromList: mnemonicWords) + let seed = try seedFactory.deriveSeed(from: mnemonic.entropy(), passphrase: password) + + return SeedFactoryResult(seed: seed.prefix(Self.seedLength), mnemonic: mnemonic) + } +} diff --git a/Tests/Common/SeedFactoryTests.swift b/Tests/Common/SeedFactoryTests.swift new file mode 100644 index 0000000..f24bb9b --- /dev/null +++ b/Tests/Common/SeedFactoryTests.swift @@ -0,0 +1,30 @@ +import XCTest +import FearlessUtils +import IrohaCrypto + +class SeedFactoryTests: XCTestCase { + func testGeneratedSeedMatchesDerived() throws { + let passwords: [String] = ["", "password"] + let strengths: [IRMnemonicStrength] = [ + .entropy128, + .entropy160, + .entropy192, + .entropy224, + .entropy256, + .entropy288, + .entropy320 + ] + + let seedFactory = SeedFactory() + + for password in passwords { + for strength in strengths { + let expectedResult = try seedFactory.createSeed(from: password, strength: strength) + let derivedResult = try seedFactory.deriveSeed(from: expectedResult.mnemonic.toString(), + password: password) + + XCTAssertEqual(expectedResult.seed, derivedResult.seed) + } + } + } +}