87 lines
3.7 KiB
Swift
87 lines
3.7 KiB
Swift
|
|
import XCTest
|
|||
|
|
@testable import MontanaApp
|
|||
|
|
|
|||
|
|
final class MnemonicTests: XCTestCase {
|
|||
|
|
/// spec, раздел "Test vectors (binding) → M-1 Vector 1"
|
|||
|
|
/// entropy = [0x00; 32]
|
|||
|
|
/// mnemonic = "abandon abandon ... abandon art"
|
|||
|
|
/// master_seed = 38a1421ac3ce191fbdc46b1cca266a9d72d22320fb38bda6a3df90a1ead664a7
|
|||
|
|
/// 8951703197be882ace38e0f557a492a8e9ff5e3c02290a8eecf5939468708edb
|
|||
|
|
func testM1Vector1_ZeroEntropy() throws {
|
|||
|
|
let entropy = Data(count: 32)
|
|||
|
|
let mnemonic = try Mnemonic.encode(entropy: entropy)
|
|||
|
|
XCTAssertEqual(mnemonic.split(whereSeparator: { $0.isWhitespace }).count, 24)
|
|||
|
|
XCTAssertTrue(mnemonic.hasSuffix(" art"), "BIP-39 standard: zero entropy + checksum 0x66 → word 'art'")
|
|||
|
|
|
|||
|
|
let masterSeed = try Mnemonic.decodeAndDerive(mnemonic: mnemonic)
|
|||
|
|
let hex = masterSeed.map { String(format: "%02x", $0) }.joined()
|
|||
|
|
XCTAssertEqual(hex,
|
|||
|
|
"38a1421ac3ce191fbdc46b1cca266a9d72d22320fb38bda6a3df90a1ead664a7" +
|
|||
|
|
"8951703197be882ace38e0f557a492a8e9ff5e3c02290a8eecf5939468708edb"
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// spec, раздел "Test vectors (binding) → M-1 Vector 2"
|
|||
|
|
/// entropy = [0xFF; 32]
|
|||
|
|
/// master_seed = a5925c51583447a0abe43b65dbc591f3780a91c7d44c6b333975a211096039f3
|
|||
|
|
/// d1d0ca9e125aa4e756f0a35b0006378ac69450e8254e32f16409a350f3ca9104
|
|||
|
|
func testM1Vector2_MaxEntropy() throws {
|
|||
|
|
let entropy = Data(repeating: 0xFF, count: 32)
|
|||
|
|
let mnemonic = try Mnemonic.encode(entropy: entropy)
|
|||
|
|
XCTAssertTrue(mnemonic.hasSuffix(" vote"))
|
|||
|
|
|
|||
|
|
let masterSeed = try Mnemonic.decodeAndDerive(mnemonic: mnemonic)
|
|||
|
|
let hex = masterSeed.map { String(format: "%02x", $0) }.joined()
|
|||
|
|
XCTAssertEqual(hex,
|
|||
|
|
"a5925c51583447a0abe43b65dbc591f3780a91c7d44c6b333975a211096039f3" +
|
|||
|
|
"d1d0ca9e125aa4e756f0a35b0006378ac69450e8254e32f16409a350f3ca9104"
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func testWordlistFingerprint() {
|
|||
|
|
XCTAssertTrue(Wordlist.verifyBindingFingerprint(),
|
|||
|
|
"wordlist.txt SHA-256 должен соответствовать spec binding fingerprint")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func testGenerateProduces24Words() {
|
|||
|
|
let m = Mnemonic.generate()
|
|||
|
|
XCTAssertEqual(m.split(whereSeparator: { $0.isWhitespace }).count, 24)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func testRoundtrip() throws {
|
|||
|
|
let entropy = Data((0..<32).map { _ in UInt8.random(in: 0...255) })
|
|||
|
|
let mnemonic = try Mnemonic.encode(entropy: entropy)
|
|||
|
|
let seed1 = try Mnemonic.decodeAndDerive(mnemonic: mnemonic)
|
|||
|
|
let seed2 = try Mnemonic.decodeAndDerive(mnemonic: mnemonic)
|
|||
|
|
XCTAssertEqual(seed1, seed2, "Determinism")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func testInvalidWordCount() {
|
|||
|
|
XCTAssertThrowsError(try Mnemonic.decodeAndDerive(mnemonic: "abandon abandon")) { err in
|
|||
|
|
if case Mnemonic.MnemonicError.wordCount(let n) = err {
|
|||
|
|
XCTAssertEqual(n, 2)
|
|||
|
|
} else {
|
|||
|
|
XCTFail("expected wordCount error")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func testInvalidWord() {
|
|||
|
|
let bad = Array(repeating: "abandon", count: 23).joined(separator: " ") + " bogusword"
|
|||
|
|
XCTAssertThrowsError(try Mnemonic.decodeAndDerive(mnemonic: bad)) { err in
|
|||
|
|
if case Mnemonic.MnemonicError.unknownWord(let pos) = err {
|
|||
|
|
XCTAssertEqual(pos, 23)
|
|||
|
|
} else {
|
|||
|
|
XCTFail("expected unknownWord error")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func testInvalidChecksum() {
|
|||
|
|
let m = Array(repeating: "abandon", count: 24).joined(separator: " ")
|
|||
|
|
XCTAssertThrowsError(try Mnemonic.decodeAndDerive(mnemonic: m)) { err in
|
|||
|
|
XCTAssertEqual(err as? Mnemonic.MnemonicError, .checksumMismatch)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|