SwiftMnemonic - Reference implementation of BIP-0039: Mnemonic code for generating deterministic keys
SwiftMnemonic is a comprehensive Swift implementation of BIP-39 (Mnemonic code for generating deterministic keys). It provides secure mnemonic phrase generation, validation, and seed derivation with support for 12 languages as specified in the BIP-39 standard.
- β Complete BIP-39 Implementation: Full compliance with the BIP-39 specification
- β 12 Language Support: English, Chinese (Simplified & Traditional), Czech, French, Italian, Japanese, Korean, Portuguese, Russian, Spanish, Turkish
- β Multiple Word Counts: Support for 12, 15, 18, 21, and 24-word mnemonics
- β Entropy Conversion: Bidirectional conversion between entropy and mnemonic phrases
- β Seed Derivation: PBKDF2-based seed generation with optional passphrase
- β Language Detection: Automatic detection of mnemonic language
- β Word Expansion: Partial word matching and expansion
- β HD Wallet Support: Extended private key (xprv) generation
- β Comprehensive Testing: Extensive test coverage with BIP-39 test vectors
- In Xcode, go to
File
βAdd Package Dependencies...
- Enter the repository URL:
https://github.com/Kingpin-Apps/swift-mnemonic.git
- Select the version or branch you want to use
- Click
Add Package
Add SwiftMnemonic as a dependency in your Package.swift
file:
dependencies: [
.package(url: "https://github.com/Kingpin-Apps/swift-mnemonic.git", from: "1.0.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: ["SwiftMnemonic"]
)
]
import SwiftMnemonic
// Create a mnemonic with a randomly generated 24-word phrase
let mnemonic = try Mnemonic(language: .english, wordCount: .twentyFour)
print("Generated mnemonic: \(mnemonic.phrase.joined(separator: " "))")
// Or create from existing entropy
let entropy = Data([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f])
let mnemonicFromEntropy = try Mnemonic(language: .english, entropy: entropy)
// Or restore from an existing mnemonic phrase
let words = ["abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "about"]
let restoredMnemonic = try Mnemonic(from: words)
print("Restored language: \(restoredMnemonic.language)")
import SwiftMnemonic
// Generate different word count mnemonics
let words12 = try Mnemonic(language: .english, wordCount: .twelve) // 128-bit entropy
let words15 = try Mnemonic(language: .english, wordCount: .fifteen) // 160-bit entropy
let words18 = try Mnemonic(language: .english, wordCount: .eighteen) // 192-bit entropy
let words21 = try Mnemonic(language: .english, wordCount: .twentyOne) // 224-bit entropy
let words24 = try Mnemonic(language: .english, wordCount: .twentyFour) // 256-bit entropy
// Access the generated phrase
print("12-word mnemonic: \(words12.phrase.joined(separator: " "))")
print("24-word mnemonic: \(words24.phrase.joined(separator: " "))")
// You can also generate on-demand (legacy method)
let generator = try Mnemonic(language: .english)
let generatedPhrase = try generator.generate(wordCount: .twelve)
print("Generated: \(generatedPhrase.joined(separator: " "))")
// Generate mnemonic in different languages
let englishMnemonic = try Mnemonic(language: .english, wordCount: .twelve)
let japaneseMnemonic = try Mnemonic(language: .japanese, wordCount: .twelve)
let frenchMnemonic = try Mnemonic(language: .french, wordCount: .twelve)
// Access phrases with appropriate delimiters automatically handled
print("Japanese: \(japaneseMnemonic.phrase.joined(separator: japaneseMnemonic.delimiter))")
print("French: \(frenchMnemonic.phrase.joined(separator: frenchMnemonic.delimiter))")
// Or use the convenience method to get properly formatted strings
let japaneseString = try japaneseMnemonic.toMnemonicString(entropy: japaneseMnemonic.entropy)
let frenchString = try frenchMnemonic.toMnemonicString(entropy: frenchMnemonic.entropy)
print("Japanese formatted: \(japaneseString)")
print("French formatted: \(frenchString)")
let mnemonic = try Mnemonic(language: .english)
let testPhrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// Check if mnemonic is valid
let isValid = try mnemonic.check(mnemonic: testPhrase)
print("Is valid: \(isValid)") // true
// Invalid mnemonic will return false
let invalidPhrase = "invalid mnemonic phrase here"
let isInvalid = try mnemonic.check(mnemonic: invalidPhrase)
print("Is invalid: \(isInvalid)") // false
// Create entropy from hex string (16 bytes = 128-bit entropy for 12 words)
let entropyHex = "0123456789abcdef0123456789abcdef"
let entropyData = Data(entropyHex.hexStringToData())
// Create mnemonic from specific entropy
let mnemonic = try Mnemonic(language: .english, entropy: entropyData)
print("Mnemonic from entropy: \(mnemonic.phrase.joined(separator: " "))")
// Access the original entropy
print("Original entropy hex: \(mnemonic.entropy.hexEncodedString())")
// Convert mnemonic phrase back to entropy (static method)
let phrase = ["abandon", "abandon", "abandon", "abandon", "abandon", "abandon",
"abandon", "abandon", "abandon", "abandon", "abandon", "about"]
let recoveredEntropy = try Mnemonic.toEntropy(phrase, wordlist: try Language.english.words())
print("Recovered entropy: \(recoveredEntropy.hexEncodedString())")
// Generate mnemonic from entropy (static method)
let generatedPhrase = try Mnemonic.toMnemonic(entropy: entropyData, wordlist: try Language.english.words())
print("Generated phrase: \(generatedPhrase.joined(separator: " "))")
let mnemonicPhrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// Generate seed without passphrase
let seed = try Mnemonic.toSeed(mnemonic: mnemonicPhrase)
print("Seed: \(seed.hexEncodedString())")
// Generate seed with passphrase
let seedWithPassphrase = try Mnemonic.toSeed(mnemonic: mnemonicPhrase, passphrase: "mypassphrase")
print("Seed with passphrase: \(seedWithPassphrase.hexEncodedString())")
// Generate HD wallet master key (xprv)
let masterKey = try Mnemonic.toHDMasterKey(seed: seed)
print("Master Key: \(masterKey)")
// Generate testnet master key
let testnetMasterKey = try Mnemonic.toHDMasterKey(seed: seed, testnet: true)
print("Testnet Master Key: \(testnetMasterKey)")
// Detect language from a mnemonic phrase (static method)
let detectedLang1 = try Mnemonic.detectLanguage(phrase: "abandon about")
print("Detected: \(detectedLang1)") // .english
// Works with partial words too
let detectedLang2 = try Mnemonic.detectLanguage(phrase: "aba abo")
print("Detected: \(detectedLang2)") // .english
// Can distinguish between similar languages
let detectedFrench = try Mnemonic.detectLanguage(phrase: "abandon aboutir")
print("Detected: \(detectedFrench)") // .french
// Use detected language to create mnemonic from existing phrase
let existingPhrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
let detectedLanguage = try Mnemonic.detectLanguage(phrase: existingPhrase)
let mnemonicFromPhrase = try Mnemonic(from: existingPhrase.split(separator: " ").map(String.init))
print("Detected and restored: \(mnemonicFromPhrase.language)")
let mnemonic = try Mnemonic(language: .english)
// Expand partial words
let expandedWord = mnemonic.expandWord(prefix: "aba")
print("Expanded: \(expandedWord)") // "abandon"
// Expand full mnemonic with partial words
let partialMnemonic = "aba abo acc"
let expandedMnemonic = mnemonic.expand(mnemonic: partialMnemonic)
print("Expanded mnemonic: \(expandedMnemonic)") // "abandon about access"
// If word is ambiguous or not found, returns original
let ambiguous = mnemonic.expandWord(prefix: "ac")
print("Ambiguous: \(ambiguous)") // "ac" (multiple matches)
// Use a custom wordlist (must be exactly 2048 words)
let customWordlist: [String] = loadYourCustomWordlist() // Your implementation
let customMnemonic = try Mnemonic(language: .english, wordlist: customWordlist)
// Use it like any other mnemonic
let phrase = try customMnemonic.generate()
do {
let mnemonic = try Mnemonic(language: .english)
let phrase = try mnemonic.generate(wordCount: .twentyFour)
let seed = try Mnemonic.toSeed(mnemonic: phrase.joined(separator: " "))
print("Success!")
} catch MnemonicError.invalidWordlistLength(let message) {
print("Invalid wordlist: \(message)")
} catch MnemonicError.languageNotDetected(let message) {
print("Language detection failed: \(message)")
} catch MnemonicError.failedChecksum(let message) {
print("Checksum validation failed: \(message)")
} catch MnemonicError.wordNotFound(let message) {
print("Word not found: \(message)")
} catch {
print("Other error: \(error)")
}
import SwiftMnemonic
func createWallet() throws {
// 1. Generate a secure 24-word mnemonic
let mnemonic = try Mnemonic(language: .english, wordCount: .twentyFour)
let mnemonicString = mnemonic.phrase.joined(separator: " ")
print("π Your mnemonic phrase (keep it safe!):")
print(mnemonicString)
// 2. Validate the generated mnemonic (should always be valid for generated mnemonics)
let isValid = try mnemonic.check(mnemonic: mnemonicString)
guard isValid else {
throw MnemonicError.failedChecksum("Generated mnemonic is invalid")
}
// 3. Derive seed with optional passphrase
let passphrase = "" // Use empty string or prompt user for passphrase
let seed = try Mnemonic.toSeed(mnemonic: mnemonicString, passphrase: passphrase)
print("π± Seed: \(seed.hexEncodedString())")
// 4. Generate master private key for HD wallet
let masterKey = try Mnemonic.toHDMasterKey(seed: seed)
print("π Master Key: \(masterKey)")
// 5. Alternative: restore from existing mnemonic
func restoreWallet(from words: [String]) throws {
let restoredMnemonic = try Mnemonic(from: words)
print("Restored mnemonic language: \(restoredMnemonic.language)")
let restoredSeed = try Mnemonic.toSeed(mnemonic: words.joined(separator: " "), passphrase: passphrase)
let restoredMasterKey = try Mnemonic.toHDMasterKey(seed: restoredSeed)
print("π Restored Master Key: \(restoredMasterKey)")
}
// 6. Store securely (pseudocode)
// secureStorage.store(mnemonic: mnemonicString)
// secureStorage.store(masterKey: masterKey)
}
// Usage
do {
try createWallet()
} catch {
print("β Error creating wallet: \(error)")
}
-
Mnemonic Storage: Never store mnemonic phrases in plain text. Use secure storage mechanisms provided by your platform (Keychain on iOS/macOS, encrypted databases, etc.).
-
Passphrase Protection: Consider using a passphrase for additional security. Passphrases should be stored separately from mnemonics.
-
Random Number Generation: This library uses the system's secure random number generator. Ensure your deployment environment has proper entropy sources.
-
Memory Management: Sensitive data like seeds and private keys should be cleared from memory when no longer needed.
-
Network Security: Never transmit mnemonic phrases or seeds over unsecured connections.
-
Backup Security: When backing up mnemonics, use secure, offline methods. Consider physical storage in secure locations.
// Example: Secure cleanup
var seed = try Mnemonic.toSeed(mnemonic: mnemonicPhrase)
// ... use seed ...
// Clear sensitive data
seed.resetBytes(in: seed.startIndex..<seed.endIndex)
- β iOS: 14.0+
- β macOS: 11.0+
- β watchOS: 7.0+
- β tvOS: 14.0+
- β Swift: 6.0+
Language | Code | Delimiter | Status |
---|---|---|---|
Chinese (Simplified) | chinese_simplified |
Space | β |
Chinese (Traditional) | chinese_traditional |
Space | β |
Czech | czech |
Space | β |
English | english |
Space | β |
French | french |
Space | β |
Italian | italian |
Space | β |
Japanese | japanese |
Full-width space (\u{3000} ) |
β |
Korean | korean |
Space | β |
Portuguese | portuguese |
Space | β |
Russian | russian |
Space | β |
Spanish | spanish |
Space | β |
Turkish | turkish |
Space | β |
- UncommonCrypto: Provides cryptographic functions (SHA2, HMAC, PBKDF2)
- SwiftBase58: Base58 encoding for extended keys
The library includes comprehensive tests with 99.35% code coverage, covering:
- All BIP-39 official test vectors
- All supported languages and word count combinations
- Edge cases and error conditions
- UTF-8 normalization across different encodings
- Entropy/mnemonic bidirectional conversion
- Language detection accuracy
- Error handling for all failure modes
- Custom wordlist validation
- Comprehensive enum testing (WordCount, Language, MnemonicError)
# Run tests
swift test
# Run with verbose output
swift test -v
# Run with coverage
swift test --enable-code-coverage
The project includes Makefile targets for generating coverage reports:
# Generate coverage report and check threshold (90%)
make coverage-check
# Generate detailed coverage report
make coverage-report
# Generate HTML coverage report (requires lcov: brew install lcov)
make coverage-html
Note: Test files are automatically excluded from coverage calculations to ensure metrics reflect only source code coverage.
Contributions are welcome! Please ensure that:
- All tests pass
- New functionality includes appropriate tests
- Code follows the existing style conventions
- Cryptographic changes are thoroughly tested against BIP-39 vectors
See LICENSE
See CHANGELOG.md for version history.
For questions, issues, or contributions:
- Open an issue on GitHub
- Check existing documentation and examples
- Review the BIP-39 specification for technical details
Disclaimer: This library is provided as-is. Users are responsible for implementing proper security practices when handling cryptographic keys and mnemonic phrases in production applications.