Skip to content

Commit

Permalink
Major cleanup of Mnemonic Generate method
Browse files Browse the repository at this point in the history
  • Loading branch information
Sajjon committed Sep 18, 2019
1 parent 7fb919b commit d58c070
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 97 deletions.
39 changes: 5 additions & 34 deletions Sources/BitcoinKit/Core/Mnemonic/BitArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ import Foundation
/// standard array such as constant time random access and
/// amortized constant time insertion at the end of the array.
///
/// Conforms to `MutableCollection`, `ExpressibleByArrayLiteral`
/// Conforms to `MutableCollection`, `RangeReplaceableCollection`, `ExpressibleByArrayLiteral`
/// , `Equatable`, `Hashable`, `CustomStringConvertible`
public struct BitArray {

public struct BitArray: Hashable, RangeReplaceableCollection {

// MARK: Creating a BitArray

Expand Down Expand Up @@ -310,13 +311,10 @@ extension BitArray: CustomStringConvertible {

/// A string containing a suitable textual
/// representation of the bit array.
public var description: String { return map { "\($0 == true ? 1 : 0)" }.joined() }
public var description: String { return binaryString }
public var binaryString: String { return map { "\($0 == true ? 1 : 0)" }.joined() }
}

extension BitArray: Equatable {}

// MARK: BitArray Equatable Protocol Conformance

/// Returns `true` if and only if the bit arrays contain the same bits in the same order.
public func == (lhs: BitArray, rhs: BitArray) -> Bool {
if lhs.count != rhs.count || lhs.cardinality != rhs.cardinality {
Expand All @@ -325,29 +323,6 @@ public func == (lhs: BitArray, rhs: BitArray) -> Bool {
return lhs.elementsEqual(rhs)
}

// MARK: Hashable Protocol Conformance
extension BitArray: Hashable {}

public extension BitArray {

/// Equivalence to Python's operator on lists: `[:n]`, e.g. `x = [1, 2, 3, 4, 5]; x[:3] // equals: [1, 2, 3]`
func prefix(maxBitCount: Int) -> BitArray {
return BitArray(self.prefix(maxBitCount))
}

/// Equivalent to Python's `[-n]`, e.g.`"Hello"[-3] // equals: "llo"`
func suffix(maxBitCount: Int) -> BitArray {
return BitArray(self.suffix(maxBitCount))
}

/// Equivalence to Python's operator on string: `[:-n]`, e.g.`"Hello"[:-3] // equals: "He"`
func prefix(subtractFromCount n: Int) -> BitArray {
let bitCount = count - n
guard bitCount > 0 else { return [] }
return prefix(maxBitCount: bitCount)
}
}

public extension BitArray {
func asBoolArray() -> [Bool] {
return self.map { $0 }
Expand All @@ -370,7 +345,3 @@ public extension BitArray {
return Data(self.asBytesArray())
}
}

extension BitArray: RangeReplaceableCollection {

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ public extension Mnemonic {
let checksumLength = mnemonicWords.count / 3

let dataBits = bitArray.prefix(subtractFromCount: checksumLength)
let checksumBits = bitArray.suffix(maxBitCount: checksumLength)
let checksumBits = bitArray.suffix(maxCount: checksumLength)

let hash = Crypto.sha256(dataBits.asData())

let hashBits = BitArray(data: hash).prefix(maxBitCount: checksumLength)
let hashBits = BitArray(data: hash).prefix(maxCount: checksumLength)

guard hashBits == checksumBits else {
throw MnemonicError.validationError(.checksumMismatch)
Expand Down
1 change: 1 addition & 0 deletions Sources/BitcoinKit/Core/Mnemonic/Mnemonic+Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public typealias MnemonicError = Mnemonic.Error
public extension Mnemonic {
enum Error: Swift.Error {
case randomBytesError
case unsupportedByteCountOfEntropy(got: Int)
indirect case validationError(ValidationError)
}
}
Expand Down
39 changes: 13 additions & 26 deletions Sources/BitcoinKit/Core/Mnemonic/Mnemonic+Generate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ import BitcoinKitPrivate
// MARK: Generate
public extension Mnemonic {
static func generate(strength: Strength = .default, language: Language = .english) throws -> [String] {
let byteCount = strength.rawValue / bitsPerByte
var bytes = Data(count: byteCount)
let status = bytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, byteCount, $0.baseAddress.unsafelyUnwrapped) }
guard status == errSecSuccess else { throw MnemonicError.randomBytesError }
return try generate(entropy: bytes, language: language)
let entropy = try securelyGenerateBytes(count: strength.byteCount)
return try generate(entropy: entropy, language: language)
}
}

Expand All @@ -33,25 +30,24 @@ internal extension Mnemonic {
language: Language = .english
) throws -> [String] {

guard let strength = Mnemonic.Strength(byteCount: entropy.count) else {
throw Error.unsupportedByteCountOfEntropy(got: entropy.count)
}

let words = wordList(for: language)
let hash = Crypto.sha256(entropy)

let entropyBinaryString = entropy.binaryString
let hashBinaryString = hash.binaryString
let checkSum = String(hashBinaryString.prefix((entropy.count * bitsPerByte) / 32))
let checkSumBit = BitArray(data: hash).prefix(strength.checksumLengthInBits)

let concatenatedBits = entropyBinaryString + checkSum
let bits = BitArray(data: entropy) + checkSumBit

var mnemonic: [String] = []
for index in 0..<(concatenatedBits.count / wordListSizeLog2) {
let startIndex = concatenatedBits.index(concatenatedBits.startIndex, offsetBy: index * wordListSizeLog2)
let endIndex = concatenatedBits.index(startIndex, offsetBy: wordListSizeLog2)
let wordIndex = Int(strtoul(String(concatenatedBits[startIndex..<endIndex]), nil, 2))
mnemonic.append(String(words[wordIndex]))
}
let wordIndices = bits.splitIntoChunks(ofSize: wordListSizeLog2)
.map { UInt11(bitArray: $0)! }
.map { $0.asInt }

try validateChecksumOf(mnemonic: mnemonic, language: language)
let mnemonic = wordIndices.map { words[$0] }

try validateChecksumOf(mnemonic: mnemonic, language: language)
return mnemonic
}
}
Expand All @@ -73,12 +69,3 @@ public extension Mnemonic {
return seed
}
}

internal func intToBinString<I>(_ int: I) -> String where I: BinaryInteger {
guard let uint8 = UInt8(exactly: int) else { fatalError("could not create uint8 from integer: \(int)") }
return byteToBinString(byte: uint8)
}

internal func byteToBinString(byte: UInt8) -> String {
return String(("00000000" + String(byte, radix: 2)).suffix(8))
}
18 changes: 17 additions & 1 deletion Sources/BitcoinKit/Core/Mnemonic/Mnemonic+Strength.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ public extension Mnemonic.Strength {
else { return nil }
self = strength
}

init?(byteCount: Int) {
let bitCount = byteCount * bitsPerByte
guard
let strength = Self(rawValue: bitCount)
else { return nil }
self = strength
}
}

// MARK: - Internal
Expand All @@ -43,6 +51,10 @@ internal extension Mnemonic.Strength {
return wordCount
}

var byteCount: Int {
return rawValue / bitsPerByte
}

static func wordCountFrom(entropyInBits: Int) -> Int {
return Int(ceil(Double(entropyInBits) / Double(wordListSizeLog2)))
}
Expand All @@ -54,9 +66,13 @@ internal extension Mnemonic.Strength {
}

static let checksumBitsPerWord = 3
var checksumLength: Int {
var checksumLengthInBits: Int {
return wordCount.wordCount / Mnemonic.Strength.checksumBitsPerWord
}

var checksumLengthInBytes: Int {
return checksumLengthInBits / bitsPerByte
}
}

// MARK: - WordCount
Expand Down
27 changes: 20 additions & 7 deletions Sources/BitcoinKit/Core/Mnemonic/UInt11.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,18 @@ struct UInt11: ExpressibleByIntegerLiteral {

private let valueBoundBy16Bits: UInt16

init?<T>(exactly source: T) where T: BinaryInteger {
guard
let valueBoundBy16Bits = UInt16(exactly: source),
valueBoundBy16Bits < 2048 else { return nil }

init?(valueBoundBy16Bits: UInt16) {
if valueBoundBy16Bits > UInt11.max16 {
return nil
}
self.valueBoundBy16Bits = valueBoundBy16Bits
}

init?<T>(exactly source: T) where T: BinaryInteger {
guard let valueBoundBy16Bits = UInt16(exactly: source) else { return nil }
self.init(valueBoundBy16Bits: valueBoundBy16Bits)
}

}

extension UInt11 {
Expand All @@ -52,8 +56,8 @@ extension UInt11 {

/// Creates a new integer value from the given string and radix.
init?<S>(_ text: S, radix: Int = 10) where S: StringProtocol {
guard let uint16 = UInt16(text, radix: radix) else { return nil }
self.init(exactly: uint16)
guard let uint16 = UInt16(text, radix: radix) else { return nil }
self.init(valueBoundBy16Bits: uint16)
}

init(integerLiteral value: Int) {
Expand All @@ -62,6 +66,11 @@ extension UInt11 {
}
self = exactly
}

init?(bitArray: BitArray) {
if bitArray.count > UInt11.bitWidth { return nil }
self.init(bitArray.binaryString, radix: 2)
}
}

extension UInt11 {
Expand All @@ -70,4 +79,8 @@ extension UInt11 {
assert(UInt16(binaryString, radix: 2)! == valueBoundBy16Bits, "incorrect conversion.")
return binaryString
}

var asInt: Int {
return Int(valueBoundBy16Bits)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,32 @@
//
import Foundation

extension RangeReplaceableCollection {
extension RangeReplaceableCollection where Self: MutableCollection {

mutating func prepend(element: Element, toLength expectedLength: Int?) {
self = prepending(element: element, toLength: expectedLength)
}
/// Equivalence to Python's operator on lists: `[:n]`, e.g. `x = [1, 2, 3, 4, 5]; x[:3] // equals: [1, 2, 3]`
func prefix(maxCount: Int) -> Self {
return Self(self.prefix(maxCount))
}

func prepending(element: Element, toLength expectedLength: Int?) -> Self {
guard let expectedLength = expectedLength else {
return self
}
var modified = self
while modified.count < expectedLength {
modified = [element] + modified
}
return modified
}
/// Equivalent to Python's `[-n]`, e.g.`"Hello"[-3] // equals: "llo"`
func suffix(maxCount: Int) -> Self {
return Self(self.suffix(maxCount))
}

mutating func append(element: Element, toLength expectedLength: Int?) {
self = appending(element: element, toLength: expectedLength)
}
/// Equivalence to Python's operator on string: `[:-n]`, e.g.`"Hello"[:-3] // equals: "He"`
func prefix(subtractFromCount n: Int) -> Self {
let specifiedCount = count - n
guard specifiedCount > 0 else { return Self() }
return prefix(maxCount: specifiedCount)
}

func appending(element: Element, toLength expectedLength: Int?) -> Self {
guard let expectedLength = expectedLength else {
return self
}
var modified = self
while modified.count < expectedLength {
modified.append(element)
}
return modified
}
func splitIntoChunks(ofSize maxLength: Int) -> [Self] {
precondition(maxLength > 0, "groups must be greater than zero")
var start = startIndex
return stride(from: 0, to: count, by: maxLength).map { _ in
let end = index(start, offsetBy: maxLength, limitedBy: endIndex) ?? endIndex
defer { start = end }
return Self(self[start..<end])
}
}
}

0 comments on commit d58c070

Please sign in to comment.