-
Notifications
You must be signed in to change notification settings - Fork 458
Update Mnemonic creation to support arrays #687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
89dc957
209c2d7
2931a3b
fa4517a
b5a68e4
8dc6f99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,50 +69,88 @@ public enum BIP39Language { | |
} | ||
|
||
public class BIP39 { | ||
/** | ||
Initializes a new mnemonics set with the provided bitsOfEntropy. | ||
**/ | ||
Comment on lines
+72
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something to remove. |
||
/// Initializes a new mnemonics set with the provided bitsOfEntropy. | ||
/// - Parameters: | ||
/// - bitsOfEntropy: 128 - 12 words, 192 - 18 words , 256 - 24 words in output. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
/// - language: words language, default english | ||
/// - Returns: random 12-24 words, that represent new Mnemonic phrase. | ||
static public func generateMnemonics(bitsOfEntropy: Int, language: BIP39Language = .english) throws -> String? { | ||
guard let entropy = entropyOf(size: bitsOfEntropy) else { throw AbstractKeystoreError.noEntropyError } | ||
return generateMnemonicsFromEntropy(entropy: entropy, language: language) | ||
} | ||
|
||
static public func generateMnemonicsFromEntropy(entropy: Data, language: BIP39Language = BIP39Language.english) -> String? { | ||
guard entropy.count >= 16, entropy.count & 4 == 0 else {return nil} | ||
let checksum = entropy.sha256() | ||
let checksumBits = entropy.count*8/32 | ||
var fullEntropy = Data() | ||
fullEntropy.append(entropy) | ||
fullEntropy.append(checksum[0 ..< (checksumBits+7)/8 ]) | ||
var wordList = [String]() | ||
for i in 0 ..< fullEntropy.count*8/11 { | ||
guard let bits = fullEntropy.bitsInRange(i*11, 11) else {return nil} | ||
let index = Int(bits) | ||
guard language.words.count > index else {return nil} | ||
let word = language.words[index] | ||
wordList.append(word) | ||
static public func generateMnemonics(entropy: Int, language: BIP39Language = .english) -> [String]? { | ||
guard let entropy = entropyOf(size: entropy) else { return nil } | ||
return generateMnemonicsFrom(entropy: entropy, language: language) | ||
} | ||
|
||
static private func entropyOf(size: Int) -> Data? { | ||
guard size >= 128 && size <= 256 && size.isMultiple(of: 32) else { | ||
return nil | ||
} | ||
|
||
return Data.randomBytes(length: size/8) | ||
} | ||
|
||
static func bitarray(from data: Data) -> String { | ||
data.map { | ||
let binary = String($0, radix: 2) | ||
let padding = String(repeating: "0", count: 8 - binary.count) | ||
return padding + binary | ||
}.joined() | ||
} | ||
|
||
static func generateChecksum(entropyBytes inputData: Data, checksumLength: Int) -> String? { | ||
guard let checksumData = inputData.sha256().bitsInRange(0, checksumLength) else { | ||
return nil | ||
} | ||
let checksum = String(checksumData, radix: 2).leftPadding(toLength: checksumLength, withPad: "0") | ||
return checksum | ||
} | ||
|
||
static public func generateMnemonicsFromEntropy(entropy: Data, language: BIP39Language = .english) -> String? { | ||
guard entropy.count >= 16, entropy.count & 4 == 0 else {return nil} | ||
let separator = language.separator | ||
let wordList = generateMnemonicsFrom(entropy: entropy) | ||
return wordList.joined(separator: separator) | ||
} | ||
|
||
/// Initializes a new mnemonics set with the provided bitsOfEntropy. | ||
/// - Parameters: | ||
/// - bitsOfEntropy: 128 - 12 words, 192 - 18 words , 256 - 24 words in output. | ||
/// - language: words language, default english | ||
/// - Returns: random 12-24 words, that represent new Mnemonic phrase. | ||
static public func generateMnemonics(bitsOfEntropy: Int, language: BIP39Language = BIP39Language.english) throws -> String? { | ||
guard bitsOfEntropy >= 128 && bitsOfEntropy <= 256 && bitsOfEntropy.isMultiple(of: 32) else {return nil} | ||
guard let entropy = Data.randomBytes(length: bitsOfEntropy/8) else {throw AbstractKeystoreError.noEntropyError} | ||
return BIP39.generateMnemonicsFromEntropy(entropy: entropy, language: | ||
language) | ||
static public func generateMnemonicsFrom(entropy: Data, language: BIP39Language = .english) -> [String] { | ||
let entropyBitSize = entropy.count * 8 | ||
let checksum_length = entropyBitSize / 32 | ||
|
||
var entropy_bits = bitarray(from: entropy) | ||
|
||
guard let checksumTest = generateChecksum(entropyBytes: entropy, checksumLength: checksum_length) else { | ||
return [] | ||
} | ||
entropy_bits += checksumTest | ||
return entropy_bits | ||
.split(every: 11) | ||
.compactMap { binary in | ||
Int(binary, radix: 2) | ||
} | ||
.map { index in | ||
language.words[index] | ||
} | ||
Comment on lines
+133
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting is required. |
||
} | ||
|
||
static public func mnemonicsToEntropy(_ mnemonics: String, language: BIP39Language = BIP39Language.english) -> Data? { | ||
let wordList = mnemonics.components(separatedBy: " ") | ||
guard wordList.count >= 12 && wordList.count.isMultiple(of: 3) && wordList.count <= 24 else {return nil} | ||
static public func mnemonicsToEntropy(_ mnemonics: String, language: BIP39Language = .english) -> Data? { | ||
let wordList = mnemonics.components(separatedBy: language.separator) | ||
return mnemonicsToEntropy(wordList, language: language) | ||
} | ||
|
||
static public func mnemonicsToEntropy(_ mnemonics: [String], language: BIP39Language = .english) -> Data? { | ||
guard mnemonics.count >= 12 && mnemonics.count.isMultiple(of: 3) && mnemonics.count <= 24 else {return nil} | ||
var bitString = "" | ||
for word in wordList { | ||
let idx = language.words.firstIndex(of: word) | ||
if idx == nil { | ||
for word in mnemonics { | ||
guard let idx = language.words.firstIndex(of: word) else { | ||
return nil | ||
} | ||
let idxAsInt = language.words.startIndex.distance(to: idx!) | ||
let stringForm = String(UInt16(idxAsInt), radix: 2).leftPadding(toLength: 11, withPad: "0") | ||
let stringForm = String(UInt16(idx), radix: 2).leftPadding(toLength: 11, withPad: "0") | ||
bitString.append(stringForm) | ||
} | ||
let stringCount = bitString.count | ||
|
@@ -131,23 +169,34 @@ public class BIP39 { | |
return entropy | ||
} | ||
|
||
static public func seedFromMmemonics(_ mnemonics: String, password: String = "", language: BIP39Language = BIP39Language.english) -> Data? { | ||
let valid = BIP39.mnemonicsToEntropy(mnemonics, language: language) != nil | ||
if !valid { | ||
|
||
//========================================= | ||
|
||
static public func seedFromMmemonics(_ mnemonics: [String], password: String = "", language: BIP39Language = .english) -> Data? { | ||
let wordList = mnemonics.joined(separator: language.separator) | ||
return seedFromMmemonics(wordList, password: password, language: language) | ||
} | ||
|
||
static public func seedFromMmemonics(_ mnemonics: String, password: String = "", language: BIP39Language = .english) -> Data? { | ||
if mnemonicsToEntropy(mnemonics, language: language) == nil { | ||
return nil | ||
} | ||
return dataFrom(mnemonics: mnemonics, password: password) | ||
} | ||
|
||
static private func dataFrom(mnemonics: String, password: String) -> Data? { | ||
guard let mnemData = mnemonics.decomposedStringWithCompatibilityMapping.data(using: .utf8) else {return nil} | ||
let salt = "mnemonic" + password | ||
guard let saltData = salt.decomposedStringWithCompatibilityMapping.data(using: .utf8) else {return nil} | ||
guard let seedArray = try? PKCS5.PBKDF2(password: mnemData.bytes, salt: saltData.bytes, iterations: 2048, keyLength: 64, variant: HMAC.Variant.sha2(.sha512)).calculate() else {return nil} | ||
let seed = Data(seedArray) | ||
return seed | ||
return Data(seedArray) | ||
} | ||
|
||
static public func seedFromEntropy(_ entropy: Data, password: String = "", language: BIP39Language = BIP39Language.english) -> Data? { | ||
guard let mnemonics = BIP39.generateMnemonicsFromEntropy(entropy: entropy, language: language) else { | ||
static public func seedFromEntropy(_ entropy: Data, password: String = "", language: BIP39Language = .english) -> Data? { | ||
guard let mnemonics = generateMnemonicsFromEntropy(entropy: entropy, language: language) else { | ||
return nil | ||
} | ||
return BIP39.seedFromMmemonics(mnemonics, password: password, language: language) | ||
return seedFromMmemonics(mnemonics, password: password, language: language) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,10 @@ | |
// | ||
|
||
import Foundation | ||
import Metal | ||
|
||
public extension Data { | ||
|
||
extension Data { | ||
init<T>(fromArray values: [T]) { | ||
let values = values | ||
let ptrUB = values.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer) in return ptr } | ||
|
@@ -33,32 +35,38 @@ extension Data { | |
return difference == UInt8(0x00) | ||
} | ||
|
||
public static func zero(_ data: inout Data) { | ||
static func zero(_ data: inout Data) { | ||
let count = data.count | ||
data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in | ||
body.baseAddress?.assumingMemoryBound(to: UInt8.self).initialize(repeating: 0, count: count) | ||
} | ||
} | ||
|
||
public static func randomBytes(length: Int) -> Data? { | ||
for _ in 0...1024 { | ||
var data = Data(repeating: 0, count: length) | ||
let result = data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) -> Int32? in | ||
if let bodyAddress = body.baseAddress, body.count > 0 { | ||
let pointer = bodyAddress.assumingMemoryBound(to: UInt8.self) | ||
return SecRandomCopyBytes(kSecRandomDefault, length, pointer) | ||
} else { | ||
return nil | ||
} | ||
} | ||
if let notNilResult = result, notNilResult == errSecSuccess { | ||
return data | ||
} | ||
static func randomBytes(length: Int) -> Data? { | ||
let entropy_bit_size = length//128 | ||
//# valid_entropy_bit_sizes = [128, 160, 192, 224, 256], count: [12, 15, 18, 21, 24] | ||
var entropy_bytes = [UInt8](repeating: 0, count: entropy_bit_size)// / 8) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some more code to remove |
||
|
||
let status = SecRandomCopyBytes(kSecRandomDefault, entropy_bytes.count, &entropy_bytes) | ||
|
||
if status != errSecSuccess { // Always test the status. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least |
||
} else { | ||
entropy_bytes = [UInt8](repeating: 0, count: entropy_bit_size)// / 8) | ||
arc4random_buf(&entropy_bytes, entropy_bytes.count) | ||
} | ||
return nil | ||
|
||
let source1 = MTLCreateSystemDefaultDevice()?.makeBuffer(length: length)?.hash.description.data(using: .utf8) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure but maybe you can answer: will it be compatible with all platforms we support? I suppose it should be but can say for sure. |
||
|
||
let entropyData = entropy_bytes.shuffled().map{ bit in | ||
return bit ^ (source1?.randomElement() ?? 0) | ||
|
||
} | ||
|
||
return Data(entropyData) | ||
} | ||
|
||
public func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public | ||
func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public | ||
if startingBit + length / 8 > self.count, length > 64, startingBit > 0, length >= 1 {return nil} | ||
let bytes = self[(startingBit/8) ..< (startingBit+length+7)/8] | ||
let padding = Data(repeating: 0, count: 8 - bytes.count) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: add newline characters after each comma so that it's easier to read.