/// <summary> /// Returns mnemonic (seed words) /// </summary> /// <exception cref="ObjectDisposedException"/> /// <returns>mnemonic (seed words)</returns> public string ToMnemonic() { if (entropy == null) { throw new ObjectDisposedException(nameof(BetterMnemonic)); } var stream = new FastStream(100); int version = 1; int depth = DerivationPath.Indexes.Length; Debug.Assert(version <= 0b00001111); Debug.Assert(depth <= 0b00001111); stream.Write((byte)(version << 4 | depth)); foreach (var item in DerivationPath.Indexes) { stream.Write(item); } stream.WriteWithCompactIntLength(entropy); uint[] wordIndexes = Convert8To11(stream); StringBuilder sb = new StringBuilder(wordIndexes.Length * 8); for (int i = 0; i < wordIndexes.Length; i++) { sb.Append($"{allWords[wordIndexes[i]]} "); } // no space at the end. sb.Length--; return(sb.ToString()); }
public void WriteWithCompactIntLength(byte[] data, byte[] expBuffer, int expPos) { var stream = new FastStream(expPos); stream.WriteWithCompactIntLength(data); Assert.Equal(expBuffer, stream.ToByteArray()); Helper.ComparePrivateField(stream, "position", expPos); }
/// <summary> /// Returns the 256-bit result of double SHA-256 hash of the message with the added constant used in signing operation. /// </summary> /// <exception cref="ArgumentNullException"/> /// <param name="message"> /// UTF-8 encoded message to sign (will be normalized using full compatibility decomposition form). /// <para/>Note that trailing spaces, new line character,... will not be changed here. /// Caller has to decide whether to change those /// </param> /// <returns>256-bit hash</returns> public byte[] GetBytesToSign(string message) { if (message is null) { throw new ArgumentNullException(nameof(message), "Message can not be null."); } using Sha256 hash = new Sha256(); FastStream stream = new FastStream(); stream.Write((byte)Constants.MsgSignConst.Length); stream.Write(Encoding.UTF8.GetBytes(Constants.MsgSignConst)); byte[] messageBytes = Encoding.UTF8.GetBytes(message.Normalize(NormalizationForm.FormKD)); stream.WriteWithCompactIntLength(messageBytes); byte[] result = hash.ComputeHashTwice(stream.ToByteArray()); return(result); }
/// <summary> /// Initializes a new instance of <see cref="BetterMnemonic"/> using the given arguments. /// </summary> /// <exception cref="ArgumentException"/> /// <exception cref="ArgumentNullException"/> /// <exception cref="FormatException"/> /// <param name="mnemonic">Mnemonic to use</param> /// <param name="wl">Word list to use</param> /// <param name="passPhrase">Optional passphrase to extend the key</param> public BetterMnemonic(string mnemonic, BIP0039.WordLists wl = BIP0039.WordLists.English, string passPhrase = null) { if (string.IsNullOrWhiteSpace(mnemonic)) { throw new ArgumentNullException(nameof(mnemonic), "Seed can not be null or empty!"); } allWords = BIP0039.GetAllWords(wl); string[] words = mnemonic.Normalize(NormalizationForm.FormKD) .Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (!words.All(x => allWords.Contains(x))) { throw new ArgumentException("Seed has invalid words.", nameof(mnemonic)); } uint[] wordIndexes = new uint[words.Length]; for (int i = 0; i < words.Length; i++) { wordIndexes[i] = (uint)Array.IndexOf(allWords, words[i]); } int itemIndex = 0; int bitIndex = 0; byte verDepth = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); int version = verDepth >> 4; int depth = verDepth & 0b00001111; uint[] pathIndexes = new uint[depth]; for (int i = 0; i < pathIndexes.Length; i++) { byte a1 = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); byte a2 = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); byte a3 = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); byte a4 = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); pathIndexes[i] = (uint)(a1 | a2 << 8 | a3 << 16 | a4 << 24); } DerivationPath = new BIP0032Path(pathIndexes); byte entLen = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); entropy = new byte[entLen]; for (int i = 0; i < entropy.Length; i++) { entropy[i] = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); } // Compute and compare checksum: var stream = new FastStream(100); Debug.Assert(version <= 0b00001111); Debug.Assert(depth <= 0b00001111); stream.Write((byte)(version << 4 | depth)); foreach (var item in DerivationPath.Indexes) { stream.Write(item); } stream.WriteWithCompactIntLength(entropy); using Sha256 sha = new Sha256(); byte[] hash = sha.ComputeHash(stream.ToByteArray()); int rem = wordIndexes.Length * 11 - (itemIndex * 11) - bitIndex; int hashIndex = 0; if (rem > 8) { byte cs1 = Read(wordIndexes, ref itemIndex, ref bitIndex, 8); if (hash[hashIndex++] != cs1) { throw new FormatException("Invalid checksum."); } rem -= 8; } byte cs2 = (byte)((wordIndexes[itemIndex] << (bitIndex - 3)) & 0xff); if ((hash[hashIndex] & (0xff << (8 - rem))) != cs2) { throw new FormatException("Invalid checksum."); } SetBip32(passPhrase); }