45 lines
1.9 KiB
Swift
45 lines
1.9 KiB
Swift
import Foundation
|
|
import CryptoKit
|
|
|
|
/// Recovery-phrase generation and validation.
|
|
///
|
|
/// Mirrors Android `RecoveryKeyManager.generateRecoveryPhrase()`: a phrase is
|
|
/// 10 words drawn uniformly from the bundled 248-word list, lowercased, and
|
|
/// separated by a single ASCII space.
|
|
///
|
|
/// Important correction from the Batch 1 spec: the Android source actually
|
|
/// contains **248** words, not 256. The iOS bundle copies that exact list so
|
|
/// cross-platform phrase generation stays byte-for-byte compatible.
|
|
public enum RecoveryKeyManager {
|
|
public static let phraseWordCount = 10
|
|
|
|
/// Generates a new 10-word recovery phrase.
|
|
public static func generatePhrase() throws -> String {
|
|
let words = try Wordlist.load()
|
|
var rng = SystemRandomNumberGenerator()
|
|
let indices = (0..<phraseWordCount).map { _ in Int.random(in: 0..<words.count, using: &rng) }
|
|
return indices.map { words[$0] }.joined(separator: " ")
|
|
}
|
|
|
|
/// Normalizes a recovery phrase: trim, lowercase, collapse internal whitespace,
|
|
/// join with a single space. Matches Android normalization intent.
|
|
public static func normalize(_ phrase: String) -> String {
|
|
phrase
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
.lowercased()
|
|
.components(separatedBy: .whitespacesAndNewlines)
|
|
.filter { !$0.isEmpty }
|
|
.joined(separator: " ")
|
|
}
|
|
|
|
/// Returns true if the phrase is well-formed: exactly 10 words and every word
|
|
/// appears in the bundled wordlist.
|
|
public static func isWellFormed(_ phrase: String) throws -> Bool {
|
|
let words = try Wordlist.load()
|
|
let normalized = normalize(phrase)
|
|
let parts = normalized.split(separator: " ", omittingEmptySubsequences: false)
|
|
guard parts.count == phraseWordCount else { return false }
|
|
return parts.allSatisfy { words.contains(String($0)) }
|
|
}
|
|
}
|