1 module tagion.wallet.BIP39; 2 3 import std.string : representation; 4 import tagion.basic.Debug; 5 import tagion.basic.Version : ver; 6 import tagion.crypto.random.random; 7 8 static assert(ver.LittleEndian, "At the moment bip39 only supports Little Endian"); 9 10 /* 11 https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki 12 10001111110100110100110001011001100010111110011101010000101001000000110000011001101010001100001000011101110011000100000111111100 13 0111 14 15 more omit biology blind insect faith corn crush search unveil away wedding 16 17 1010010001011010010011001111111000111100011110010110110111110111011110101101101010000000010001010111111001100010011010101000110000110100110011100000010001010001010100110111101110001010011010100000101011000001101000110000010110001001100011100001110011010010 18 01001011 19 20 21 picture sponsor display jump nothing wing twin exotic earth vessel one blur erupt acquire earn hunt media expect race ecology flat shove infant enact 22 23 1137e53d16d7ce04339914da41bdeb24b246f0878494f066a145f8a7f43c8264e177873c54830fa9b0cafdf5846258521b208f6d7fcd0de78ac22bf51040efde 24 */ 25 26 import std.algorithm; 27 import std.range; 28 import std.typecons; 29 import tagion.hibon.HiBONRecord; 30 31 @safe 32 struct WordList { 33 import tagion.crypto.pbkdf2; 34 import std.digest.sha : SHA512; 35 36 alias pbkdf2_sha512 = pbkdf2!SHA512; 37 const(ushort[string]) table; 38 const(string[]) words; 39 enum presalt = "mnemonic"; 40 this(const(string[]) list) pure nothrow 41 in (list.length == TOTAL_WORDS) 42 do { 43 words = list; 44 table = list 45 .enumerate!ushort 46 .map!(w => tuple!("index", "value")(w.value, w.index)) 47 .assocArray; 48 49 } 50 51 void gen(ref scope ushort[] words) const nothrow { 52 foreach (ref word; words) { 53 word = getRandom!ushort & (TOTAL_WORDS - 1); 54 } 55 } 56 57 const(ushort[]) mnemonicNumbers(const(string[]) mnemonics) const pure { 58 return mnemonics 59 .map!(m => table.get(m, ushort.max)) 60 .array; 61 } 62 63 bool checkMnemoicNumbers(scope const(ushort[]) mnemonic_codes) const pure nothrow @nogc { 64 return mnemonic_codes.all!(m => m < TOTAL_WORDS); 65 } 66 67 ubyte[] opCall(scope const(ushort[]) mnemonic_codes, scope const(char[]) passphrase) const nothrow { 68 scope word_list = mnemonic_codes[] 69 .map!(mnemonic_code => words[mnemonic_code]); 70 return opCall(word_list, passphrase); 71 } 72 73 char[] passphrase(const uint number_of_words) const nothrow { 74 scope ushort[] mnemonic_codes; 75 mnemonic_codes.length = number_of_words; 76 scope (exit) { 77 mnemonic_codes[] = 0; 78 } 79 gen(mnemonic_codes); 80 const password_size = mnemonic_codes 81 .map!(code => words[code]) 82 .map!(m => m.length) 83 .sum + mnemonic_codes.length - 1; 84 auto result = new char[password_size]; 85 result[] = ' '; 86 uint index; 87 foreach (code; mnemonic_codes) { 88 result[index .. index + words[code].length] = words[code]; 89 index += words[code].length + char.sizeof; 90 } 91 return result; 92 } 93 94 enum count = 2048; 95 enum dk_length = 64; 96 ubyte[] opCall(R)(scope R mnemonics, scope const(char[]) passphrase) const nothrow if (isInputRange!R) { 97 scope char[] salt = presalt ~ passphrase; 98 const password_size = mnemonics.map!(m => m.length).sum + mnemonics.length - 1; 99 scope password = new char[password_size]; 100 scope (exit) { 101 password[] = 0; 102 salt[] = 0; 103 } 104 password[] = ' '; 105 uint index; 106 foreach (mnemonic; mnemonics) { 107 password[index .. index + mnemonic.length] = mnemonic; 108 index += mnemonic.length + char.sizeof; 109 } 110 return pbkdf2_sha512(password.representation, salt.representation, count, dk_length); 111 } 112 113 enum MAX_WORDS = 24; /// Max number of mnemonic word in a string 114 enum MNEMONIC_BITS = 11; /// Bit size of the word number 2^11=2048 115 enum TOTAL_WORDS = 1 << MNEMONIC_BITS; 116 enum MAX_BITS = MAX_WORDS * MNEMONIC_BITS; /// Total number of bits 117 118 @trusted 119 ubyte[] entropy(const(ushort[]) mnemonic_codes) const { 120 import std.bitmanip : nativeToBigEndian, nativeToLittleEndian; 121 import std.stdio; 122 123 const total_bits = mnemonic_codes.length * MNEMONIC_BITS; 124 const total_bytes = total_bits / 8 + ((total_bits & 7) != 0); 125 ubyte[] result = new ubyte[total_bytes]; 126 127 foreach (i, mnemonic; mnemonic_codes) { 128 const bit_pos = i * MNEMONIC_BITS; 129 const byte_pos = bit_pos / 8; 130 const shift_pos = 32 - (11 + (bit_pos & 7)); 131 const mnemonic_bytes = (uint(mnemonic) << shift_pos).nativeToBigEndian; 132 result[byte_pos] |= mnemonic_bytes[0]; 133 result[byte_pos + 1] = mnemonic_bytes[1]; 134 if (mnemonic_bytes[2]) { 135 result[byte_pos + 2] = mnemonic_bytes[2]; 136 137 } 138 } 139 return result; 140 } 141 142 } 143 144 /* 145 https://learnmeabitcoin.com/technical/mnemonic 146 later echo alcohol essence charge eight feel sweet nephew apple aerobic device 147 01111101010010001011110000011000001001101010001001101000100011011101010100110110110111011001010001000001010101000001000010011110 148 0101 149 */ 150 151 @safe 152 unittest { 153 // import std.stdio; 154 import tagion.wallet.bip39_english; 155 import std.format; 156 import std.string : representation; 157 158 const wordlist = WordList(words); 159 { 160 const mnemonic = [ 161 "punch", 162 "shock", 163 "entire", 164 "north", 165 "file", 166 "identify" 167 ]; 168 const(ushort[]) expected_mnemonic_codes = [1390, 1586, 604, 1202, 689, 900]; 169 immutable expected_entropy = "101011011101100011001001001011100100101100100101011000101110000100"; 170 const mnemonic_codes = wordlist.mnemonicNumbers(mnemonic); 171 assert(expected_mnemonic_codes == mnemonic_codes); 172 string mnemonic_codes_bits = format("%(%011b%)", mnemonic_codes); 173 assert(expected_entropy == mnemonic_codes_bits); 174 const entropy = wordlist.entropy(mnemonic_codes); 175 string entropy_bits = format("%(%08b%)", entropy)[0 .. 11 * expected_mnemonic_codes.length]; 176 assert(expected_entropy == entropy_bits); 177 } 178 { 179 const mnemonic = [ 180 "later", 181 "echo", 182 "alcohol", 183 "essence", 184 "charge", 185 "eight", 186 "feel", 187 "sweet", 188 "nephew", 189 "apple", 190 "aerobic", 191 "device" 192 ]; 193 // const(ushort[]) mnemonic_code =[1390, 1586, 604, 1202, 689, 900]; 194 immutable expected_entropy = "011111010100100010111100000110000010011010100010011010001000110111010101001101101101110110010100010000010101010000010000100111100101"; 195 const mnemonic_codes = wordlist.mnemonicNumbers(mnemonic); 196 string entropy_bits = format("%(%011b%)", wordlist.mnemonicNumbers(mnemonic)); 197 assert(expected_entropy == entropy_bits); 198 199 } 200 { /// PBKDF2 BIP39 201 const mnemonic = [ 202 "basket", 203 "actual" 204 ]; 205 206 import tagion.crypto.pbkdf2; 207 import std.bitmanip : nativeToBigEndian; 208 import std.digest.sha : SHA512; 209 210 const mnemonic_codes = wordlist.mnemonicNumbers(mnemonic); 211 212 const entropy = wordlist.entropy(mnemonic_codes); 213 string mnemonic_codes_bits = format("%(%011b%)", mnemonic_codes); 214 string entropy_bits = format("%(%08b%)", entropy); //[0 .. 12 * mnemonic_codes.length]; 215 alias pbkdf2_sha512 = pbkdf2!SHA512; 216 string salt = "mnemonic"; //.representation; 217 const entropy1 = "basket actual".representation; 218 const result1 = pbkdf2_sha512(entropy1, salt.representation, 2048, 64); 219 } 220 }