45 lines
1.8 KiB
Swift
45 lines
1.8 KiB
Swift
import Foundation
|
||
import CryptoKit
|
||
|
||
/// spec, раздел "Мнемоника и seed → Каноническая wordlist".
|
||
/// 2048 lowercase ASCII слов, sorted lexicographically.
|
||
/// Binding fingerprint: SHA-256(canonical_bytes) =
|
||
/// 2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda
|
||
enum Wordlist {
|
||
static let canonical: [String] = {
|
||
guard let url = Bundle.main.url(forResource: "wordlist", withExtension: "txt"),
|
||
let raw = try? String(contentsOf: url, encoding: .ascii)
|
||
else {
|
||
fatalError("Resources/wordlist.txt отсутствует или повреждён")
|
||
}
|
||
let words = raw.split(separator: "\n").map { String($0) }
|
||
precondition(words.count == 2048, "wordlist должен содержать ровно 2048 слов, получено \(words.count)")
|
||
return words
|
||
}()
|
||
|
||
/// Binary search index слова в canonical wordlist.
|
||
static func index(of word: String) -> UInt16? {
|
||
var lo = 0
|
||
var hi = canonical.count
|
||
while lo < hi {
|
||
let mid = (lo + hi) / 2
|
||
if canonical[mid] < word {
|
||
lo = mid + 1
|
||
} else if canonical[mid] > word {
|
||
hi = mid
|
||
} else {
|
||
return UInt16(mid)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
/// Verify binding fingerprint при старте приложения (spec обязательное правило).
|
||
static func verifyBindingFingerprint() -> Bool {
|
||
let canonicalBytes = canonical.map { $0 + "\n" }.joined().data(using: .ascii)!
|
||
let hash = SHA256.hash(data: canonicalBytes)
|
||
let hex = hash.map { String(format: "%02x", $0) }.joined()
|
||
return hex == "2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda"
|
||
}
|
||
}
|