In this article we will talk about the BIP39 Algorithm that is being used in many crypto. wallets.
So what does this algorithm do?
In order to generate a deterministic wallet we need to have a mnemonic code or mnemonic sentence ( a group of easy to remember words)[1]
The BIP algorithm describes hot to implement this mnemonic code. Then we can use dictionary of words to create a mnemonic sentence.
- Initially we create an entropy. The entropy is an array of bits and has to be a multiple of 32 bits. The more the entropy the more secure the code is. In Cardano the allowed size of ENT is 128-256. Let’s call the entropy ENT
- The second step is to generate a Checksum. To do so we take the first ENT / 32 bits of the entropy SHA25 hash.
- Then we append this Checksum to the initial entropy
- We split the bits in groups of 11
- From each of this groups we read their number which isa number from 0-2047. This number serves as index to the wordlist.
- We find the words from wordlist and we join them
This is our mnemonic sentence.
Let’s now solve it in Swift:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
enum EntropyLength { case bits128, bits160, bits192, bits224, bits256 var size: Int { switch self { case .bits128: return 128 case .bits160: return 160 case .bits192: return 192 case .bits224: return 224 case .bits256: return 256 } } } protocol CryptoProviding { func generateRandomBytes(withSize size: Int) -> [UInt8] func sha256(of data: [UInt8]) -> [UInt8] } struct BIP39 { let cryptoProvider: CryptoProviding init(crypto: CryptoProviding) { self.cryptoProvider = crypto } func bip39(withENT ent: EntropyLength) -> [UInt16] { let entropy = cryptoProvider.generateRandomBytes(withSize: ent.size) let sha = cryptoProvider.sha256(of: entropy) let checkSum = sha[0] >> (8 - ((entropy.count * 8) / 32)) return entropyPlusChecksumGrouped( in: entropy, checkSum: checkSum ) } private func entropyPlusChecksumGrouped(in input: [UInt8], checkSum: UInt8) -> [UInt16] { let mask: UInt16 = 0b0000011111111111 let input16 = input.map { UInt16($0) } let count = (input.count * 8 + (input.count * 8 / 32)) / 11 var output: [UInt16] = Array<UInt16>(repeating: 0, count: count) for (index, value) in input16.enumerated() { let firstWordIndex = (index * 8) / 11 let rightOffset = (index * 8) % 11 output[firstWordIndex] = output[firstWordIndex] | ((value << 3) >> rightOffset) let lastWordIndex = (index * 8 + 8) / 11 let leftOffset = 11 - (index * 8 + 8) % 11 output[lastWordIndex] = output[lastWordIndex] | (value << leftOffset) & mask } output[count - 1] = output[count - 1] | UInt16(checkSum) return Array(output) } } |
We have TDD the above code with my Friend Greg in a nice TDD session.
You can find the unit tests here:
https://github.com/arnot-project/swift-bip39/blob/main/Tests/arnotTests/BIP39Tests.swift
and the code swift package here:
https://github.com/arnot-project/swift-bip39
For the wordlist we can use the following:
https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt
[1] https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki