Mnemonic Code
Mnemonic Code (Mnemonic Sentence, Mnemonic Phrase 라고도 함)는 바이너리 데이타를 사람이 기억하기 쉬운 단어들의 집합으로 변환한 코드를 말하는데, 흔히, 암호화폐의 HD Wallet (Hierarchical Deterministic Wallet) 에서 널리 사용되고 있다. Mnemonic Code는 각 단어에 1:1로 매핑되는 Binary 코드를 변환하여 Binary Seed로 사용된다.
비트코인의 BIP39 (Bitcoin Improvement Proposal 0039)에는 Mnemonic Code와 Binary Seed를 생성하는 절차들이 자세히 서술되어 있다. 참고로 BIP39을 비롯한 HD Wallet의 키들을 생성하는 온라인 사이트로서 https://iancoleman.io/bip39/ 이 널리 알려져 있는데, 프로그램 개발시 유용하게 사용될 수 있다.
BIP39에서 정의한 Mnemonic Phrase의 단어수는 12, 15, 18, 21, 24개 인데, 일반적으로 12개와 24개의 Mnemonic Phrase가 가장 많이 사용된다.
public enum MnemonicType { BIP39_128 = 128, // 12 words BIP39_160 = 160, // 15 words BIP39_192 = 192, // 18 words BIP39_224 = 224, // 21 words BIP39_256 = 256 // 24 words }
Mnemonic Code는 기본적으로 난수를 발생시켜, 여기에 에러 체킹을 위해 간단한 체크섬을 추가한 후, 이를 일정 크기(11비트)로 쪼개서 해당 비트값에 상응하는 단어를 만드는 것이다. Mnemonic Code를 생성하는 구체적인 절차는 다음과 같다.
- Mnemonic Code의 단어 수에 해당하는 비트수만큼의 난수(random number)를 생성한다. 불규칙하게 생성되는 이 난수를 엔트로피(entrophy)라 부른다. 예를 들어, 12개의 단어를 갖는 Mnemonic Code를 생성하기 위해서는 128비트(16바이트)의 난수를 생성한다.
- 엔트로피를 SHA256으로 해싱하여 체크섬에 사용할 비트들을 구한다. 체크섬은 엔트로피 뒤에 추가되는 비트들로서 에러를 체크하기 위해 사용된다. 체크섬의 비트수는 "(엔트로피 비트수) / 32" 계산식을 통해 구하는데, 예를 들어 128비트 엔트로피의 경우 128 / 32 = 4 가 되어 SHA256 해시결과의 앞부분 4비트가 체크섬이 된다.
- (엔트로피 + 체크섬) 데이타를 11비트씩 쪼개서, 해당 값에 매핑되는 단어를 2048개의 단어 리스트로부터 찾는다. 2^11 = 2048 이므로 0 ~ 2047과 같은 인덱스를 갖는 단어 리스트에서 11 비트값을 인덱스로 갖는 단어를 검색하게 된다. 여기에서 사용하는 단어 리스트는 BIP39에 정의되어 있으며, 거의 대부분 영어를 사용하지만, 다른 언어의 단어리스트도 있다.
public class Mnemonic { public static string Generate(MnemonicType mnemonicType) { var rng = RandomNumberGenerator.Create(); // get initial ENT int entropyBits = (int)mnemonicType; byte[] entrophy = new byte[entropyBits / 8]; rng.GetBytes(entrophy); // convert entrophy to mnemonic sentence string mnemonic = ConvertEntrophyToMnemonic(entrophy); return mnemonic; } public static string ConvertEntrophyToMnemonic(byte[] entrophy) { int entropyBits = entrophy.Length * 8; // get SHA256(ENT) using var sha = SHA256.Create(); byte[] hashEnt = sha.ComputeHash(entrophy); // checksum int csBits = entropyBits / 32; byte cs = hashEnt[0]; cs &= (byte)(0xFF << (8 - csBits)); // combine ENT+CS byte[] entcs = new byte[entrophy.Length + 1]; Array.Copy(entrophy, 0, entcs, 0, entrophy.Length); entcs[entcs.Length - 1] = cs; // split ENT+CS into groups of 11 bits (0 ~ 2047) int bitCount = entropyBits + csBits; var bits = new Bits(entcs); var sbMnemonic = new StringBuilder(); for (int i = 0; i < bitCount; i += 11) { int wordIndex = bits.GetBits(i, 11); string word = BIP39_WORDS[wordIndex]; sbMnemonic.AppendFormat("{0} ", word); } return sbMnemonic.ToString().Trim(); } // ... }
Binary Seed
Mnemonic Code는 HD Wallet에서 사용하기 위해 Binary Seed로 변환되는데, 이는 Mnemonic Code로부터 도출되는 Key를 생성하는 과정이다.
Binary Seed는 PBKDF2 함수를 사용하여 생성되는데, 입력으로 Mnemonic Code와 Salt 를 받아들인다. Salt는 BIP39에 정의된 바로는 "mnemonic"를 사용하는데, 사용자 옵션으로 추가적인 password를 사용하는 경우에는 "mnemonic" + password를 Salt로 사용한다. PBKDF2 함수에 사용하는 해시함수로는 HMAC-SHA512를 사용하고, 2048번 Iteration을 수행하고, 결과키의 크기로 512 비트를 사용한다. 이를 식으로 표현하면 아래와 같다.
BinarySeed = PBKDF2(MnemonicCode, "mnemonic" + password, HMAC-SHA512, 2048, 512)
아래는 Mnemonic Code로부터 Binary Seed를 생성하는 예제로서, 니모닉 코드에서 512비트의 Seed를 생성한다. 이 Binary Seed를 다시 HMAC-SHA512로 해싱하여 HD Wallet에서 사용하는 마스터 키를 생성한다.
public static byte[] ConvertMnemonicToSeed(string mnemonicSentence, string passphrase = null) { byte[] salt = Encoding.UTF8.GetBytes("mnemonic" + (passphrase ?? "")); // UTF-8 NFKD // 2048 iteration with HMACSHA512. // The derived key size is 64 bytes (512 bits) byte[] seed = KeyDerivation.Pbkdf2(mnemonicSentence, salt, KeyDerivationPrf.HMACSHA512, 2048, 64); return seed; }
테스트 데이타: https://github.com/trezor/python-mnemonic/blob/master/vectors.json (예) Mnemonic Code: legal winner thank year wave sausage worth useful legal winner thank yellow Binary Seed: 2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607