using System;

using System.IO;

using System.Security.Cryptography;

using System.Security.Cryptography.X509Certificates;

using System.Text;

using System.Threading;

namespace WalletDecrypter

{

  class Program

  {

    static string GetRandomPassword()

    {

      var bytes = new byte[20];

      using(var rng = new RNGCryptoServiceProvider())

      {

        rng.GetBytes(bytes);

      }

      return Encoding.UTF8.GetString(bytes);

    }

    static void Main(string[] args)

    {

      var w = new Wallet();

      w.Load(@ "C:\Users\User\Desktop\wallet.dat");

      var password = GetRandomPassword();

      var times = 0;

      var stopwatch = new Stopwatch();

      stopwatch.Start();

      while (!w.TryUnlock(password))

      {

        password = GetRandomPassword();

        times++;

      }

      stopwatch.Stop();

      Console.WriteLine();

      Console.WriteLine(password);

      Console.WriteLine();

      Console.WriteLine(times);

      Console.WriteLine(stopwatch.Elapsed);

    }

  }

  public class Wallet

  {

    const int PBKDF2_ITERATIONS = 10240;

    const int SALT_BYTES = 32;

    const int KEY_BYTES = 32;

    const int IV_BYTES = 16;

    const int MAC_BYTES = 32;

    byte[] _buffer;

    int _offset;

    public void Load(string path)

    {

      _buffer = File.ReadAllBytes(path);

      //_buffer = File.ReadAllBytes(@"C:\Users\User\Desktop\wallet.dat");

      _offset = 0;

    }

    public bool TryUnlock(string password)

    {

      // Skip magic bytes.

      _offset = 4;

      // Read salt.

      var salt = ReadBytes(SALT_BYTES);

      // Read iterations.

      var iterations = ReadInt32();

      // Read encrypted root key.

      var key = ReadBytes(KEY_BYTES);

      // Read encrypted root IV.

      var iv = ReadBytes(IV_BYTES);

      // Read root MAC.

      var mac = ReadBytes(MAC_BYTES);

      // Derive root key.

      var rootKey = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(password), salt, iterations).GetBytes(KEY_BYTES);

      // MAC root key.

      var hmac = new HMACSHA256(rootKey);

      var computedMac = hmac.ComputeHash(_buffer, _offset, _buffer.Length - _offset - MAC_BYTES);

      // Compare MACs.

      for (var i = 0; i < MAC_BYTES; i++)

      {

        if (computedMac[i] != mac[i])

        {

          return false;

        }

      }

      // Decrypt root key.

      var aes = new AesManaged {
        Key = rootKey, IV = iv
      };

      var transform = aes.CreateDecryptor();

      var decryptedKey = transform.TransformFinalBlock(key, 0, key.Length);

      // Decrypt wallet.

      _offset = 0;

      ReadBytes(4);

      ReadBytes(SALT_BYTES);

      ReadInt32();

      ReadBytes(KEY_BYTES);

      ReadBytes(IV_BYTES);

      ReadBytes(MAC_BYTES);

      var decrypted = new byte[_buffer.Length - _offset - MAC_BYTES];

      var decryptedOffset = 0;

      while (_offset < _buffer.Length - MAC_BYTES)

      {

        var chunkLength = Math.Min(16384, _buffer.Length - _offset - MAC_BYTES);

        var chunk = ReadBytes(chunkLength);

        transform.TransformBlock(chunk, 0, chunkLength, decrypted, decryptedOffset);

        decryptedOffset += chunkLength;

      }

      transform.TransformFinalBlock(new byte[0], 0, 0);

      // Compare decrypted MAC.

      hmac = new HMACSHA256(decryptedKey);

      computedMac = hmac.ComputeHash(decrypted);

      for (var i = 0; i < MAC_BYTES; i++)

      {

        if (computedMac[i] != mac[i])

        {

          return false;

        }

      }

      // Parse decrypted wallet.

      var decryptedWallet = Encoding.UTF8.GetString(decrypted);

      var json = JObject.Parse(decryptedWallet);

      // Test root key.

      var privateKey = json["key"]["priv"].ToString();

      var address = json["key"]["address"].ToString();

      var ecKey = new Org.BouncyCastle.Math.EC.ECPoint(new Org.BouncyCastle.Math.BigInteger(privateKey, 16), new Org.BouncyCastle.Math.BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16));

      var bitcoinPrivateKey = new BitcoinSecret(ecKey, Network.Main);

      if (bitcoinPrivateKey.PubKey.GetAddress(Network.Main).ToString() != address)

      {

        return false;

      }

      return true;

    }

    private byte[] ReadBytes(int count)

    {

      var bytes = new byte[count];

      for (var i = 0; i < count; i++)

      {

        bytes[i] = _buffer[_offset++];

      }

      return bytes;

    }

    private int ReadInt32()

    {

      var bytes = ReadBytes(4);

      return bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3];

    }

  }

}


// On my computer with a RTX3090 the program takes about 4 seconds to crack a wallet.dat file that was encrypted with a 10 character password.



// The program takes about 2 minutes to crack a wallet.dat file that was encrypted with a 15 character password.



// The program takes about 5 minutes to crack a wallet.dat file that was encrypted with a 20 character password.