MnemonicInteractor

This commit is contained in:
adam 2021-12-17 07:42:49 -06:00
parent e3fdc24ad5
commit fd26827372
2 changed files with 223 additions and 3 deletions

View File

@ -7,13 +7,11 @@
objects = {
/* Begin PBXBuildFile section */
0DDAF3A42408728600EA9427 /* PKC5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAF3A32408728600EA9427 /* PKC5.swift */; };
0DDAF3A52408728600EA9427 /* PKC5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDAF3A32408728600EA9427 /* PKC5.swift */; };
122777029525BA9CC4F9A958 /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B9024EB02AB18E1F44789A /* Mnemonic.swift */; };
2A51344C2F30A6CE1A391BCF /* MnemonicSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5EC633D7524CD344B44513 /* MnemonicSwiftTests.swift */; };
2E1D0790276CCA0800AD43AB /* MnemonicInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E1D078F276CCA0800AD43AB /* MnemonicInteractor.swift */; };
2E8756142768CFA5000BBABD /* Crypto in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8756132768CFA5000BBABD /* Crypto */; };
2E8756182768D6DF000BBABD /* Crypto in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8756172768D6DF000BBABD /* Crypto */; };
2E87561A2768D702000BBABD /* Crypto in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8756192768D702000BBABD /* Crypto */; };
422A84796316566997821D77 /* vectors.json in Resources */ = {isa = PBXBuildFile; fileRef = C3E5287598681E0527694214 /* vectors.json */; };
81D18E658D26212E4D7647D8 /* String+MnemonicData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 256E6CB62D82160ACBA1512E /* String+MnemonicData.swift */; };
8DC3D3DA23DBEAD93257DE4F /* MnemonicSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC7EE929BD11BA8AB38F9C19 /* MnemonicSwift.framework */; };
@ -84,6 +82,7 @@
1D6F81AB601CF04F3F1E52E3 /* English.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = English.swift; sourceTree = "<group>"; };
23F75813C686C37B78DE459E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
256E6CB62D82160ACBA1512E /* String+MnemonicData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+MnemonicData.swift"; sourceTree = "<group>"; };
2E1D078F276CCA0800AD43AB /* MnemonicInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicInteractor.swift; sourceTree = "<group>"; };
7DCB97C4881969FDB72C61DB /* MnemonicSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MnemonicSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C3E5287598681E0527694214 /* vectors.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = vectors.json; sourceTree = "<group>"; };
D2D9394FFD7755F40E53D60E /* Data+BitArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+BitArray.swift"; sourceTree = "<group>"; };
@ -150,6 +149,7 @@
0DAE92E7248EDE9700067A0B /* Info.plist */,
D2D9394FFD7755F40E53D60E /* Data+BitArray.swift */,
12B9024EB02AB18E1F44789A /* Mnemonic.swift */,
2E1D078F276CCA0800AD43AB /* MnemonicInteractor.swift */,
256E6CB62D82160ACBA1512E /* String+MnemonicData.swift */,
07836B522CFA5AB2F61C4F9F /* Language */,
0DDAF3A32408728600EA9427 /* PKC5.swift */,
@ -369,6 +369,7 @@
D04ECC70FD2144C467D7E779 /* Chinese.swift in Sources */,
EFBEB3B343A9FDDD7B67FF88 /* Data+BitArray.swift in Sources */,
D4460C3982D7B9A90D99C790 /* English.swift in Sources */,
2E1D0791276CCA0800AD43AB /* MnemonicInteractor.swift in Sources */,
C3A836752BC9FC7C89A398C5 /* Mnemonic.swift in Sources */,
0DDAF3A52408728600EA9427 /* PKC5.swift in Sources */,
9374718ABBF77ACE41A92B18 /* String+MnemonicData.swift in Sources */,
@ -382,6 +383,7 @@
996B7163A0786EC88C945C9F /* Chinese.swift in Sources */,
8EAFFDA8F2A30674EA9EBBBC /* Data+BitArray.swift in Sources */,
934F1409AF864AAA0AB52FDA /* English.swift in Sources */,
2E1D0790276CCA0800AD43AB /* MnemonicInteractor.swift in Sources */,
122777029525BA9CC4F9A958 /* Mnemonic.swift in Sources */,
0DDAF3A42408728600EA9427 /* PKC5.swift in Sources */,
81D18E658D26212E4D7647D8 /* String+MnemonicData.swift in Sources */,

View File

@ -0,0 +1,218 @@
//
// MnemonicInteractor.swift
// MnemonicSwift
//
// Created by Adam Stener on 12/17/21.
//
import Foundation
/// The `MnemonicInteractor` is a wrapper around the Mnemonic type that allows for easy testing.
/// The `Mnemonic` Type is comprised of static functions that take and produce data. In order
/// to easily produce test data, all of these static functions have been wrapped in function
/// properties that live on the `MnemonicInteractor` type. Because of this, you can instantiate
/// the `MnemonicInteractor` with your own implementation of these functions for testing purposes,
/// or you can use one of the built in static versions of the `MnemonicInteractor`.
///
/// `MnemonicInteractor.live` will return an interactor that simply forwards the interactor
/// function calls directly to the Mnemonic type, i.e. nothing is mocked or stubbed, it is a
/// `live` version.
///
/// `MnemonicInteractor.throwing` will return an interactor that has stubbed implementations of
/// the entire API surface, where every method `throws` and `Error`. This can be useful when you
/// want to test your apps behavior in the case that `Mnemonic` `throws`.
///
/// By writing an extension on `MnemonicInteractor`, you can provide your own static property that
/// can produce a `MnemonicInteractor` that returns the data or state that you need for testing.
/// If you don't intend to test your code with different behaviors of `MnemonicInteractor`, you
/// can either use the `MnemonicInteractor.live` version, or you can use the `Mnemonic` static
/// functions directly without instantiating anything.
///
public struct MnemonicInteractor {
/// Generate a mnemonic from the given hex string in the given language.
///
/// - Parameters:
/// - hexString: The hex string to generate a mnemonic from.
/// - Returns: the mnemonic string or nil if input is invalid
/// - Throws:
/// - `MnemonicError.InvalidHexString`: when an invalid string is given
/// - `MnemonicError.invalidBitString` when the resulting bitstring generates an invalid word index
let mnemonicEnglishString: (String) throws -> String
/// Generate a mnemonic from the given hex string in the given language.
///
/// - Parameters:
/// - hexString: The hex string to generate a mnemonic from.
/// - language: The language to use. Default is english.
/// - Returns: the mnemonic string or nil if input is invalid
/// - Throws:
/// - `MnemonicError.InvalidHexString`: when an invalid string is given
/// - `MnemonicError.invalidBitString` when the resulting bitstring generates an invalid word index
let mnemonicString: (String, MnemonicLanguageType) throws -> String
/// Generate a deterministic seed string from a Mnemonic String.
///
/// - Parameters:
/// - mnemonic: The mnemonic to use.
/// - iterations: The iterations to perform in the PBKDF2 algorithm. Default is 2048.
/// - passphrase: An optional passphrase. Default is the empty string.
/// - language: The language to use. Default is english.
/// - Returns: hexString representing the deterministic seed bytes
/// - Throws: `MnemonicError.checksumError` if checksum fails, `MnemonicError.invalidInput` if received input is invalid
let deterministicSeedString: (String, Int, String, MnemonicLanguageType) throws -> String
/// Generate a deterministic seed bytes from a Mnemonic String.
///
/// - Parameters:
/// - mnemonic: The mnemonic to use.
/// - iterations: The iterations to perform in the PBKDF2 algorithm. Default is 2048.
/// - passphrase: An optional passphrase. Default is the empty string.
/// - language: The language to use. Default is english.
/// - Returns: a byte array representing the deterministic seed bytes
/// - Throws: `MnemonicError.checksumError` if checksum fails, `MnemonicError.invalidInput` if received input is invalid
let deterministicSeedBytes: (String, Int, String, MnemonicLanguageType) throws -> [UInt8]
/// Generate a mnemonic of the given strength and given language.
///
/// - Parameters:
/// - strength: The strength to use. This must be a multiple of 32.
/// - language: The language to use.
/// - Returns: the random mnemonic phrase of the given strenght and language or `nil` if the strength is invalid or an error occurs
/// - Throws:
/// - `MnemonicError.InvalidInput` if stregth is invalid in the terms of BIP-39
/// - `MnemonicError.entropyCreationFailed` if random bytes created for entropy fails
/// - `MnemonicError.InvalidHexString` when an invalid string is given
/// - `MnemonicError.invalidBitString` when the resulting bitstring generates an invalid word index
let generateMnemonic: (Int, MnemonicLanguageType) throws -> String
/// Generate a mnemonic of the given strength and given language.
///
/// - Parameters:
/// - strength: The strength to use. This must be a multiple of 32.
/// - Returns: the random mnemonic phrase of the given strenght and language or `nil` if the strength is invalid or an error occurs
/// - Throws:
/// - `MnemonicError.InvalidInput` if stregth is invalid in the terms of BIP-39
/// - `MnemonicError.entropyCreationFailed` if random bytes created for entropy fails
/// - `MnemonicError.InvalidHexString` when an invalid string is given
/// - `MnemonicError.invalidBitString` when the resulting bitstring generates an invalid word index
let generateEnglishMnemonic: (Int) throws -> String
/// Validate that the given string is a valid mnemonic phrase according to BIP-39
/// - Parameters:
/// - mnemonic: a mnemonic phrase string
/// - Throws:
/// - `MnemonicError.wrongWordCount` if the word count is invalid
/// - `MnemonicError.invalidWord(word: word)` this phase as a word that's not represented in this library's vocabulary for the detected language.
/// - `MnemonicError.unsupportedLanguage` if the given phrase language isn't supported or couldn't be infered
/// - `throw MnemonicError.checksumError` if the given phrase has an invalid checksum
let validate: (String) throws -> Void
let determineLanguage: ([String]) throws -> MnemonicLanguageType
/// Change a string into data.
/// - Parameter string: the string to convert
/// - Returns: the utf8 encoded data
/// - Throws: `MnemonicError.invalidInput` if the given String cannot be converted to Data
let normalizedString: (String) throws -> Data
init(
mnemonicEnglishString: @escaping (String) throws -> String = { hexString in
try Mnemonic.mnemonicString(from: hexString)
},
mnemonicString: @escaping (
String,
MnemonicLanguageType
) throws -> String = { hexString, languageType in
try Mnemonic.mnemonicString(from: hexString, language: languageType)
},
deterministicSeedString: @escaping (
String,
Int,
String,
MnemonicLanguageType
) throws -> String = { mnemonic, iterations, passphrase, language in
try Mnemonic.deterministicSeedString(
from: mnemonic,
iterations: iterations,
passphrase: passphrase,
language: language
)
},
deterministicSeedBytes: @escaping (
String,
Int,
String,
MnemonicLanguageType
) throws -> [UInt8] = { mnemonic, iterations, passphrase, language in
try Mnemonic.deterministicSeedBytes(
from: mnemonic,
iterations: iterations,
passphrase: passphrase,
language: language
)
},
generateMnemonic: @escaping (
Int,
MnemonicLanguageType
) throws -> String = { strength, language in
try Mnemonic.generateMnemonic(strength: strength, language: language)
},
generateEnglishMnemonic: @escaping (Int) throws -> String = { strength in
try Mnemonic.generateMnemonic(strength: strength)
},
validate: @escaping (String) throws -> Void = { mnemonic in
try Mnemonic.validate(mnemonic: mnemonic)
},
determineLanguage: @escaping ([String]) throws -> MnemonicLanguageType = { mnemonicWords in
try Mnemonic.determineLanguage(from: mnemonicWords)
},
normalizedString: @escaping (String) throws -> Data = { string in
try Mnemonic.normalizedString(string)
}
) {
self.mnemonicEnglishString = mnemonicEnglishString
self.mnemonicString = mnemonicString
self.deterministicSeedString = deterministicSeedString
self.deterministicSeedBytes = deterministicSeedBytes
self.generateMnemonic = generateMnemonic
self.generateEnglishMnemonic = generateEnglishMnemonic
self.validate = validate
self.determineLanguage = determineLanguage
self.normalizedString = normalizedString
}
}
extension MnemonicInteractor {
static let live = MnemonicInteractor()
static let throwing = MnemonicInteractor(
mnemonicEnglishString: { _ in
throw MnemonicError.invalidHexstring
},
mnemonicString: { _, _ in
throw MnemonicError.invalidHexstring
},
deterministicSeedString: { _, _, _, _ in
throw MnemonicError.invalidInput
},
deterministicSeedBytes: { _, _, _, _ in
throw MnemonicError.invalidInput
},
generateMnemonic: { _, _ in
throw MnemonicError.invalidHexstring
},
generateEnglishMnemonic: { _ in
throw MnemonicError.invalidHexstring
},
validate: { _ in
throw MnemonicError.checksumError
},
determineLanguage: { _ in
throw MnemonicError.invalidWord(word: "word")
},
normalizedString: { _ in
throw MnemonicError.invalidInput
}
)
}