From 22b4481a19e48f04f4be7a8fca9354d55f43646a Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Tue, 7 Mar 2023 18:05:12 +0500 Subject: [PATCH] IOS-3059 Add seed generation and parsing --- TangemSdk/TangemSdk.xcodeproj/project.pbxproj | 16 +- .../TangemSdk/Common/Extensions/Data+.swift | 34 +++- .../TangemSdk/Common/Extensions/String+.swift | 18 +- .../Common/HDWallet/BIP39/BIP39.swift | 167 ++++++++++++++++-- .../Common/HDWallet/BIP39/EntropyLength.swift | 6 +- .../Common/HDWallet/BIP39/MnemonicError.swift | 5 + .../Common/HDWallet/BIP39/Wordlist.swift | 2 +- .../TangemSdkTests/Seed/MnemonicTests.swift | 75 ++++++-- .../Seed/mnemonic_invalid_test_vectors.json | 13 ++ .../Seed/mnemonic_valid_test_vectors.json | 56 ++++++ .../Seed/seed_test_vectors.json | 1 + 11 files changed, 349 insertions(+), 44 deletions(-) create mode 100644 TangemSdk/TangemSdkTests/Seed/mnemonic_invalid_test_vectors.json create mode 100644 TangemSdk/TangemSdkTests/Seed/mnemonic_valid_test_vectors.json diff --git a/TangemSdk/TangemSdk.xcodeproj/project.pbxproj b/TangemSdk/TangemSdk.xcodeproj/project.pbxproj index 0ac3f3c6d..f2e5cdf78 100644 --- a/TangemSdk/TangemSdk.xcodeproj/project.pbxproj +++ b/TangemSdk/TangemSdk.xcodeproj/project.pbxproj @@ -296,7 +296,9 @@ DC1244B329B60B6F0037BC05 /* BIP39.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1244B229B60B6F0037BC05 /* BIP39.swift */; }; DC1244B529B60E480037BC05 /* english.txt in Resources */ = {isa = PBXBuildFile; fileRef = DC1244B429B60E480037BC05 /* english.txt */; }; DC1244B929B610550037BC05 /* MnemonicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1244B829B610550037BC05 /* MnemonicTests.swift */; }; - DC1244BD29B61DCB0037BC05 /* seed_test_vectors.json in Resources */ = {isa = PBXBuildFile; fileRef = DC1244BC29B61DCB0037BC05 /* seed_test_vectors.json */; }; + DC1244C129B766920037BC05 /* seed_test_vectors.json in Resources */ = {isa = PBXBuildFile; fileRef = DC1244C029B766920037BC05 /* seed_test_vectors.json */; }; + DC1244C329B766B70037BC05 /* mnemonic_valid_test_vectors.json in Resources */ = {isa = PBXBuildFile; fileRef = DC1244C229B766B70037BC05 /* mnemonic_valid_test_vectors.json */; }; + DC1244C529B769400037BC05 /* mnemonic_invalid_test_vectors.json in Resources */ = {isa = PBXBuildFile; fileRef = DC1244C429B769400037BC05 /* mnemonic_invalid_test_vectors.json */; }; DC59CB0429AF597900EC14E1 /* Wordlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0329AF597900EC14E1 /* Wordlist.swift */; }; DC59CB0A29AF6F9C00EC14E1 /* EntropyLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0929AF6F9C00EC14E1 /* EntropyLength.swift */; }; DC59CB0C29AF706100EC14E1 /* MnemonicError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0B29AF706100EC14E1 /* MnemonicError.swift */; }; @@ -616,7 +618,9 @@ DC1244B229B60B6F0037BC05 /* BIP39.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BIP39.swift; sourceTree = ""; }; DC1244B429B60E480037BC05 /* english.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = english.txt; sourceTree = ""; }; DC1244B829B610550037BC05 /* MnemonicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicTests.swift; sourceTree = ""; }; - DC1244BC29B61DCB0037BC05 /* seed_test_vectors.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = seed_test_vectors.json; sourceTree = ""; }; + DC1244C029B766920037BC05 /* seed_test_vectors.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = seed_test_vectors.json; sourceTree = ""; }; + DC1244C229B766B70037BC05 /* mnemonic_valid_test_vectors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mnemonic_valid_test_vectors.json; sourceTree = ""; }; + DC1244C429B769400037BC05 /* mnemonic_invalid_test_vectors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mnemonic_invalid_test_vectors.json; sourceTree = ""; }; DC59CB0329AF597900EC14E1 /* Wordlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wordlist.swift; sourceTree = ""; }; DC59CB0929AF6F9C00EC14E1 /* EntropyLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntropyLength.swift; sourceTree = ""; }; DC59CB0B29AF706100EC14E1 /* MnemonicError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicError.swift; sourceTree = ""; }; @@ -1401,7 +1405,9 @@ isa = PBXGroup; children = ( DC1244B829B610550037BC05 /* MnemonicTests.swift */, - DC1244BC29B61DCB0037BC05 /* seed_test_vectors.json */, + DC1244C029B766920037BC05 /* seed_test_vectors.json */, + DC1244C229B766B70037BC05 /* mnemonic_valid_test_vectors.json */, + DC1244C429B769400037BC05 /* mnemonic_invalid_test_vectors.json */, ); path = Seed; sourceTree = ""; @@ -1587,8 +1593,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - DC1244BD29B61DCB0037BC05 /* seed_test_vectors.json in Resources */, 5D54408A2682260000F7D05B /* SetAccessCode.json in Resources */, + DC1244C529B769400037BC05 /* mnemonic_invalid_test_vectors.json in Resources */, 5D46F537274D68010004681F /* DeriveWalletPublicKey.json in Resources */, 5D54408E268226B600F7D05B /* Depersonalize.json in Resources */, 5D3217462684B1DB000C3AAF /* v3.05ada.json in Resources */, @@ -1601,8 +1607,10 @@ 5D544094268243F700F7D05B /* Card.json in Resources */, 5D5440982682497100F7D05B /* v4.json in Resources */, 5D4B127726D3CEE7006E173C /* ReadFiles.json in Resources */, + DC1244C129B766920037BC05 /* seed_test_vectors.json in Resources */, 5D3B9F0326BD678500532CC7 /* SignHashes.json in Resources */, 5D2BDF8626DD4869002F7E19 /* TestParseRequest.json in Resources */, + DC1244C329B766B70037BC05 /* mnemonic_valid_test_vectors.json in Resources */, 5D5440922682297A00F7D05B /* CreateWallet.json in Resources */, 5D54408C2682269400F7D05B /* SetPasscode.json in Resources */, 5D4B127D26D3D351006E173C /* WriteFiles.json in Resources */, diff --git a/TangemSdk/TangemSdk/Common/Extensions/Data+.swift b/TangemSdk/TangemSdk/Common/Extensions/Data+.swift index 925fb9402..be3ced7ff 100644 --- a/TangemSdk/TangemSdk/Common/Extensions/Data+.swift +++ b/TangemSdk/TangemSdk/Common/Extensions/Data+.swift @@ -82,7 +82,30 @@ extension Data { public init(_ byte: Byte) { self = Data([byte]) } - + + init?(bitsString: String) { + let byteLength = 8 + + guard bitsString.count % byteLength == 0 else { + return nil + } + + let binaryBytes = Array(bitsString).chunked(into: byteLength) + + var bytes = [UInt8]() + bytes.reserveCapacity(bitsString.count / byteLength) + + for binaryByte in binaryBytes { + guard let byte = UInt8(String(binaryByte), radix: 2) else { + return nil + } + + bytes.append(byte) + } + + self = Data(bytes) + } + @available(iOS 13.0, *) public func getSha256() -> Data { let digest = SHA256.hash(data: self) @@ -149,8 +172,13 @@ extension Data { } @available(iOS 13.0, *) - public func pbkdf2sha256(salt: Data, rounds: Int) throws -> Data { - return try pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), salt: salt, keyByteCount: 32, rounds: rounds) + public func pbkdf2sha256(salt: Data, rounds: Int, keyByteCount: Int = 32) throws -> Data { + return try pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), salt: salt, keyByteCount: keyByteCount, rounds: rounds) + } + + @available(iOS 13.0, *) + public func pbkdf2sha512(salt: Data, rounds: Int, keyByteCount: Int = 64) throws -> Data { + return try pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), salt: salt, keyByteCount: keyByteCount, rounds: rounds) } //SO14443A diff --git a/TangemSdk/TangemSdk/Common/Extensions/String+.swift b/TangemSdk/TangemSdk/Common/Extensions/String+.swift index 3aa161d21..35fdf999d 100644 --- a/TangemSdk/TangemSdk/Common/Extensions/String+.swift +++ b/TangemSdk/TangemSdk/Common/Extensions/String+.swift @@ -58,18 +58,12 @@ public extension String { internal func trim() -> String { return trimmingCharacters(in: .whitespacesAndNewlines) } - - internal func camelCaseToSnakeCase() -> String { - let acronymPattern = "([A-Z]+)([A-Z][a-z]|[0-9])" - let normalPattern = "([a-z0-9])([A-Z])" - return self.processCamelCaseRegex(pattern: acronymPattern)? - .processCamelCaseRegex(pattern: normalPattern)?.lowercased() ?? self.lowercased() - } - - private func processCamelCaseRegex(pattern: String) -> String? { - let regex = try? NSRegularExpression(pattern: pattern, options: []) - let range = NSRange(location: 0, length: count) - return regex?.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2") + + internal func zeroPadding(toLength newLength: Int) -> String { + guard count < newLength else { return self } + + let prefix = Array(repeating: "0", count: newLength - count).joined() + return prefix + self } } diff --git a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/BIP39.swift b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/BIP39.swift index b66cf51bb..508b9408c 100644 --- a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/BIP39.swift +++ b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/BIP39.swift @@ -12,9 +12,9 @@ import Foundation public struct BIP39 { /// Generate a mnemonic. /// - Parameters: - /// - entropyLength: An entropy length to use. Default is 128 bit. - /// - wordlist: A wordlist to use. Default is english. - /// - Returns: Generated mnemonic + /// - entropyLength: The entropy length to use. Default is 128 bit. + /// - wordlist: The wordlist to use. Default is english. + /// - Returns: The generated mnemonic splitted to components public func generateMnemonic(entropyLength: EntropyLength = .bits128, wordlist: Wordlist = .en) throws -> [String] { guard entropyLength.rawValue % 32 == 0 else { throw MnemonicError.mnenmonicCreationFailed @@ -25,19 +25,129 @@ public struct BIP39 { return try generateMnemonic(from: entropyData, wordlist: wordlist) } + /// Generate a determenistic seed + /// - Parameters: + /// - mnemonic: The mnemonic to use + /// - passphrase: The passphrase to use. Default is no passphrase (empty). + /// - Returns: The generated seed + public func generateSeed(from mnemonicComponents: [String], passphrase: String = "") throws -> Data { + try validate(mnemonicComponents: mnemonicComponents) + + let mnemonicString = convertToMnemonicString(mnemonicComponents) + let normalizedMnemonic = try normalizedData(from: mnemonicString) + let normalizedSalt = try normalizedData(from: Constants.seedSaltPrefix + passphrase) + let seed = try normalizedMnemonic.pbkdf2sha512(salt: normalizedSalt, rounds: 2048) + return seed + } + + public func convertToMnemonicString(_ mnemonicComponents: [String]) -> String { + return mnemonicComponents.joined(separator: " ") + } + + public func parse(mnemonicString: String) throws -> [String] { + let regex = try NSRegularExpression(pattern: "[a-zA-Z]+") + let range = NSRange(location: 0, length: mnemonicString.count) + let matches = regex.matches(in: mnemonicString, range: range) + let components = matches.compactMap { result -> String? in + guard result.numberOfRanges > 0, + let stringRange = Range(result.range(at: 0), in: mnemonicString) else { + return nil + } + + return String(mnemonicString[stringRange]).trim().lowercased() + } + + try validate(mnemonicComponents: components) + return components + } + + private static func extractCaptureGroupString( + from result: NSTextCheckingResult, + at index: Int, + in text: String + ) -> String? { + guard index < result.numberOfRanges, + let stringRange = Range( + result.range(at: index), + in: text + ) else { + return nil + } + + return String(text[stringRange]) + } + + public func validate(mnemonicComponents: [String]) throws { + // Validate words count + guard !mnemonicComponents.isEmpty else { + throw MnemonicError.wrongWordCount + } + + guard let entropyLength = EntropyLength.allCases.first(where: { $0.wordsCount == mnemonicComponents.count }) else { + throw MnemonicError.wrongWordCount + } + + // Validate wordlist by the first word + let wordlist = try getWordlist(by: mnemonicComponents[0]) + + // Validate all the words + var invalidWords = Set() + + // Generate an indices array inplace + var concatenatedBits = "" + + for word in mnemonicComponents { + guard let wordIndex = wordlist.firstIndex(of: word) else { + invalidWords.insert(word) + continue + } + + let indexBits = String(wordIndex, radix: 2).zeroPadding(toLength: 11) + concatenatedBits.append(contentsOf: indexBits) + } + + guard invalidWords.isEmpty else { + throw MnemonicError.invalidWords(words: Array(invalidWords)) + } + + // Validate checksum + + let checksumBitsCount = mnemonicComponents.count / 3 + guard checksumBitsCount == entropyLength.cheksumBitsCount else { + throw MnemonicError.invalidCheksum + } + + let entropyBitsCount = concatenatedBits.count - checksumBitsCount + let entropyBits = String(concatenatedBits.prefix(entropyBitsCount)) + let checksumBits = String(concatenatedBits.suffix(checksumBitsCount)) + + guard let entropyData = Data(bitsString: entropyBits) else { + throw MnemonicError.invalidCheksum + } + + let calculatedChecksumBits = entropyData + .getSha256() + .toBits() + .prefix(entropyLength.cheksumBitsCount) + .joined() + + guard calculatedChecksumBits == checksumBits else { + throw MnemonicError.invalidCheksum + } + } + /// Generate a mnemonic from data. Useful for testing purposes. /// - Parameters: - /// - data: Entropy data in hex format - /// - wordlist: A wordlist to use. - /// - Returns: Generated mnemonic + /// - data: The entropy data in hex format + /// - wordlist: The wordlist to use. + /// - Returns: The generated mnemonic func generateMnemonic(from entropyData: Data, wordlist: Wordlist) throws -> [String] { guard let entropyLength = EntropyLength(rawValue: entropyData.count * 8) else { throw MnemonicError.invalidEntropyLength } let entropyHashBits = entropyData.getSha256().toBits() - let checksumBitLength = entropyLength.rawValue / 32 - let entropyChecksumBits = entropyHashBits.prefix(checksumBitLength) + let entropyChecksumBits = entropyHashBits.prefix(entropyLength.cheksumBitsCount) let entropyBits = entropyData.toBits() let concatenatedBits = entropyBits + entropyChecksumBits @@ -51,11 +161,46 @@ public struct BIP39 { let allWords = wordlist.words let maxWordIndex = allWords.count - guard indexes.allSatisfy({ $0 < maxWordIndex }) else { - throw MnemonicError.mnenmonicCreationFailed + let words = try indexes.map { index in + guard index < maxWordIndex else { + throw MnemonicError.mnenmonicCreationFailed + } + + return allWords[index] + } - let words = indexes.map { allWords[$0] } return words } + + private func normalizedData(from string: String) throws -> Data { + let normalizedString = string.decomposedStringWithCompatibilityMapping + + guard let data = normalizedString.data(using: .utf8) else { + throw MnemonicError.normalizationFailed + } + + return data + } + + private func getWordlist(by word: String) throws -> [String] { + for list in Wordlist.allCases { + let words = list.words + + if words.contains(word) { + return words + } + } + + throw MnemonicError.unsupportedLanguage + } +} + +// MARK: - Constants + +@available(iOS 13.0, *) +private extension BIP39 { + enum Constants { + static let seedSaltPrefix = "mnemonic" + } } diff --git a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/EntropyLength.swift b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/EntropyLength.swift index 5aaddac97..d69319f80 100644 --- a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/EntropyLength.swift +++ b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/EntropyLength.swift @@ -8,7 +8,7 @@ import Foundation -public enum EntropyLength: Int { +public enum EntropyLength: Int, CaseIterable { case bits128 = 128 case bits160 = 160 case bits192 = 192 @@ -24,4 +24,8 @@ public enum EntropyLength: Int { case .bits256: return 24 } } + + var cheksumBitsCount: Int { + rawValue / 32 + } } diff --git a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/MnemonicError.swift b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/MnemonicError.swift index 970d2db13..b15aea2d8 100644 --- a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/MnemonicError.swift +++ b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/MnemonicError.swift @@ -12,5 +12,10 @@ public enum MnemonicError: Error { case invalidEntropyLength case invalidWordCount case invalidWordsFile + case invalidCheksum case mnenmonicCreationFailed + case normalizationFailed + case wrongWordCount + case unsupportedLanguage + case invalidWords(words: [String]) } diff --git a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/Wordlist.swift b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/Wordlist.swift index af312989d..dc75cf63b 100644 --- a/TangemSdk/TangemSdk/Common/HDWallet/BIP39/Wordlist.swift +++ b/TangemSdk/TangemSdk/Common/HDWallet/BIP39/Wordlist.swift @@ -9,7 +9,7 @@ import Foundation @available(iOS 13.0, *) -public enum Wordlist { +public enum Wordlist: CaseIterable { case en /// This var reads a big array from a file diff --git a/TangemSdk/TangemSdkTests/Seed/MnemonicTests.swift b/TangemSdk/TangemSdkTests/Seed/MnemonicTests.swift index 1337e9ff2..8ce146953 100644 --- a/TangemSdk/TangemSdkTests/Seed/MnemonicTests.swift +++ b/TangemSdk/TangemSdkTests/Seed/MnemonicTests.swift @@ -13,7 +13,7 @@ import XCTest @available(iOS 13.0, *) class MnemonicTests: XCTestCase { func testReadWords() { - let langs: [Wordlist] = [.en] + let langs = Wordlist.allCases for lang in langs { XCTAssertTrue(lang.words.count > 0) @@ -21,8 +21,8 @@ class MnemonicTests: XCTestCase { } func testMnemonicGenerationBase() throws { - let entropyLengthArray: [EntropyLength] = [.bits128, .bits160, .bits192, .bits224, .bits256] - let wordLists: [Wordlist] = [.en] + let entropyLengthArray = EntropyLength.allCases + let wordLists = Wordlist.allCases let bip39 = BIP39() @@ -34,8 +34,8 @@ class MnemonicTests: XCTestCase { } } - func testMnemonicGenerationByEnVectors() throws { - guard let allVectors = try getTestVectors(), + func testMnemonicByEnVectors() throws { + guard let allVectors = try getTestVectors(from: Constants.seedTestVectorsFilename), let vectors = allVectors[Constants.englishTestVectors] as? [[String]] else { XCTFail("Failed to parse test vectors file.") return @@ -46,21 +46,68 @@ class MnemonicTests: XCTestCase { for vector in vectors { let entropy = vector[0] let expectedMnemonic = vector[1] - let mnemonic = (try bip39.generateMnemonic(from: Data(hexString: entropy), wordlist: .en)).joined(separator: " ") - XCTAssertEqual(mnemonic, expectedMnemonic) + let expectedSeed = vector[2] + + let mnemonic = try bip39.generateMnemonic(from: Data(hexString: entropy), wordlist: .en) + let mnemonicString = bip39.convertToMnemonicString(mnemonic) + XCTAssertEqual(mnemonicString, expectedMnemonic) + + let seed = try bip39.generateSeed(from: mnemonic, passphrase: Constants.passphrase) + XCTAssertEqual(seed.hexString.lowercased(), expectedSeed) + } + } + + func testParseMnemonic() throws { + guard let allVectors = try getTestVectors(from: Constants.mnemonicValidTestVectorsFilename), + let vectors = allVectors[Constants.englishTestVectors] as? [[String]] else { + XCTFail("Failed to parse test vectors file.") + return } + + let bip39 = BIP39() + + for vector in vectors { + let mnemonicToParse = vector[0] + let expectedMnemonic = vector[1] + + let parsedMnemonic = try bip39.parse(mnemonicString: mnemonicToParse) + let parsedMnemonicString = bip39.convertToMnemonicString(parsedMnemonic) + XCTAssertEqual(parsedMnemonicString, expectedMnemonic) + } + } + + func testParseInvalidMnemonic() throws { + guard let allVectors = try getTestVectors(from: Constants.mnemonicInvalidTestVectorsFilename), + let vectors = allVectors[Constants.englishTestVectors] as? [[String]], + let firstVector = vectors.first else { + XCTFail("Failed to parse test vectors file.") + return + } + + let bip39 = BIP39() + + for cases in firstVector { + XCTAssertThrowsError(try bip39.parse(mnemonicString: cases)) + } + } + + func testSwapWords() throws { + let bip39 = BIP39() + let valid = "legal winner thank year wave sausage worth useful legal winner thank yellow" + var components = valid.split(separator: " ") + components.swapAt(3, 4) + let invalid = components.joined(separator: " ") + XCTAssertThrowsError(try bip39.parse(mnemonicString: invalid)) } - private func getTestVectors() throws -> [String: Any]? { - guard let url = Bundle(for: MnemonicTests.self).url(forResource: "seed_test_vectors", withExtension: "json") else { + private func getTestVectors(from filename: String) throws -> [String: Any]? { + guard let url = Bundle(for: MnemonicTests.self).url(forResource: filename, withExtension: "json") else { return nil } let data = try Data(contentsOf: url) - let options: JSONSerialization.ReadingOptions = [.allowFragments, .mutableContainers, .mutableLeaves] - guard let dictionary = - try JSONSerialization.jsonObject(with: data, options: options) as? [String: Any] else { + guard let dictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return nil } @@ -72,5 +119,9 @@ class MnemonicTests: XCTestCase { private extension MnemonicTests { enum Constants { static let englishTestVectors = "english" + static let passphrase = "TREZOR" + static let seedTestVectorsFilename = "seed_test_vectors" + static let mnemonicValidTestVectorsFilename = "mnemonic_valid_test_vectors" + static let mnemonicInvalidTestVectorsFilename = "mnemonic_invalid_test_vectors" } } diff --git a/TangemSdk/TangemSdkTests/Seed/mnemonic_invalid_test_vectors.json b/TangemSdk/TangemSdkTests/Seed/mnemonic_invalid_test_vectors.json new file mode 100644 index 000000000..dc35f3565 --- /dev/null +++ b/TangemSdk/TangemSdkTests/Seed/mnemonic_invalid_test_vectors.json @@ -0,0 +1,13 @@ +{ + "english": [ + [ + "legal winner", + "pen winner pen tangem wave sausage worth useful legal winner thank yellow", + "ручка дом крыша солнце ручка носок карта замок забор дрова булка вещь", + "legal winner thank year wave sausage worth useful legal winner thank yellow yellow", + "legal winner thank year 亂 sausage worth useful legal winner thank yellow", + "", + "pear peasant pelican pen pear peasant pelican pen pear peasant pelican pen pear peasant pelican pen" + ] + ] +} diff --git a/TangemSdk/TangemSdkTests/Seed/mnemonic_valid_test_vectors.json b/TangemSdk/TangemSdkTests/Seed/mnemonic_valid_test_vectors.json new file mode 100644 index 000000000..d66399625 --- /dev/null +++ b/TangemSdk/TangemSdkTests/Seed/mnemonic_valid_test_vectors.json @@ -0,0 +1,56 @@ +{ + "english": [ + [ + "legal winner thank year wave sausage worth useful legal winner thank yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "Legal winner thank year wave sausage worth useful legal winner thank yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "Legal winner thank YEAR WaVe sausage worth useful legal winner thank yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + " Legal\t\twinner\t\tthank\t\tYEAR\t\tWaVe\n\nsausage\t\nworth useful\tlegal\twinner thank yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "1. legal 2. winner 3. thank 4. year 5. wave 6. sausage 7. worth 8. useful 9. legal 10. winner 11. thank 12. yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "1) legal 2) winner 3) thank 4) year 5) wave 6) sausage 7) worth 8) useful 9) legal 10) winner 11) thank 12) yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "legal;winner;thank;year;wave;sausage;worth;useful;legal;winner;thank;yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "legal-winner-thank-year-wave-sausage-worth-useful-legal-winner-thank-yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "legal,winner,thank,year,wave,sausage,worth,useful,legal,winner,thank,yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "legal, winner, thank, year, wave, sausage, worth, useful, legal, winner, thank, yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "legal, winner, thank, year,\nwave, sausage, worth, useful,\nlegal, winner, thank, yellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + " legal winner thank year wave sausage worth useful legal winner thank yellow ", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + [ + "legal\nwinner\nthank\nyear\nwave\nsausage\nworth\nuseful\nlegal\nwinner\nthank\nyellow", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + ], + ] +} diff --git a/TangemSdk/TangemSdkTests/Seed/seed_test_vectors.json b/TangemSdk/TangemSdkTests/Seed/seed_test_vectors.json index 5c02e37c1..3b506ea5f 100644 --- a/TangemSdk/TangemSdkTests/Seed/seed_test_vectors.json +++ b/TangemSdk/TangemSdkTests/Seed/seed_test_vectors.json @@ -146,3 +146,4 @@ ] ] } +