Determine if a Bitcoin wallet address is "valid"

Yes, the Bitcoin-Address-Utility project is an open-source C# tool that includes this kind of verification. In particular, look at Casascius.Bitcoin.Util.Base58CheckToByteArray().


I pieced together a simple version of Casascius.Bitcoin.Util.Base58CheckToByteArray() that works with dotnet core 2.0 - The only reference is to -->Org.BouncyCastle.Crypto.Digests;

public class Validator
{
    public static bool IsValidAddress(string Address)
    {
        byte[] hex = Base58CheckToByteArray(Address);
        if (hex == null || hex.Length != 21)
            return false;
        else
            return true;
    }

    public static byte[] Base58CheckToByteArray(string base58)
    {

        byte[] bb = Base58.ToByteArray(base58);
        if (bb == null || bb.Length < 4) return null;

        Sha256Digest bcsha256a = new Sha256Digest();
        bcsha256a.BlockUpdate(bb, 0, bb.Length - 4);

        byte[] checksum = new byte[32];  
        bcsha256a.DoFinal(checksum, 0);
        bcsha256a.BlockUpdate(checksum, 0, 32);
        bcsha256a.DoFinal(checksum, 0);

        for (int i = 0; i < 4; i++)
        {
            if (checksum[i] != bb[bb.Length - 4 + i]) return null;
        }

        byte[] rv = new byte[bb.Length - 4];
        Array.Copy(bb, 0, rv, 0, bb.Length - 4);
        return rv;
    }

}

} - borrowed from above

public class Base58
{
    /// <summary>
    /// Converts a base-58 string to a byte array, returning null if it wasn't valid.
    /// </summary>
    public static byte[] ToByteArray(string base58)
    {
        Org.BouncyCastle.Math.BigInteger bi2 = new Org.BouncyCastle.Math.BigInteger("0");
        string b58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

        foreach (char c in base58)
        {
            if (b58.IndexOf(c) != -1)
            {
                bi2 = bi2.Multiply(new Org.BouncyCastle.Math.BigInteger("58"));
                bi2 = bi2.Add(new Org.BouncyCastle.Math.BigInteger(b58.IndexOf(c).ToString()));
            }
            else
            {
                return null;
            }
        }

        byte[] bb = bi2.ToByteArrayUnsigned();

        // interpret leading '1's as leading zero bytes
        foreach (char c in base58)
        {
            if (c != '1') break;
            byte[] bbb = new byte[bb.Length + 1];
            Array.Copy(bb, 0, bbb, 1, bb.Length);
            bb = bbb;
        }

        return bb;
    }

    public static string FromByteArray(byte[] ba)
    {
        Org.BouncyCastle.Math.BigInteger addrremain = new Org.BouncyCastle.Math.BigInteger(1, ba);

        Org.BouncyCastle.Math.BigInteger big0 = new Org.BouncyCastle.Math.BigInteger("0");
        Org.BouncyCastle.Math.BigInteger big58 = new Org.BouncyCastle.Math.BigInteger("58");

        string b58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

        string rv = "";

        while (addrremain.CompareTo(big0) > 0)
        {
            int d = Convert.ToInt32(addrremain.Mod(big58).ToString());
            addrremain = addrremain.Divide(big58);
            rv = b58.Substring(d, 1) + rv;
        }

        // handle leading zeroes
        foreach (byte b in ba)
        {
            if (b != 0) break;
            rv = "1" + rv;

        }
        return rv;
    }

}

} - Tests

[TestClass]
public class ValidatorTests
{
    [TestMethod]
    public void IsValidAddress_Test_AbnCoin()
    {
        var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
        Assert.IsTrue(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_BitCoin()
    {
        var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
        Assert.IsTrue(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_BitCoinTestnet()
    {
        var Address = "mpMwtvqaLQ4rCJsnoceAoLShKb4inV8uUi";
        Assert.IsTrue(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_BitCoinGold()
    {
        var Address = "GRiDm3LEjXAMMJhWaYqN8nSjuU7PSqZMUe";
        Assert.IsTrue(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_Dash()
    {
        var Address = "Xb9Edf28eYR9RRDwj7MBBVBc5vgGgT2vLV";
        Assert.IsTrue(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_Litecoin()
    {
        var Address = "LUdpZosHDP3M97ZSfvj3p1qygNFMNpXBr3";
        Assert.IsTrue(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_False_TooShort()
    {
        var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSm";
        Assert.IsFalse(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_False_TooLong()
    {
        var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSmJS";
        Assert.IsFalse(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_Test_False_BadChecksum()
    {
        var Address = "1QF5NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
        Assert.IsFalse(Validator.IsValidAddress(Address));
    }

    [TestMethod]
    public void IsValidAddress_False_NotBase58()
    {
        var Address = "lQF4NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
        Assert.IsFalse(Validator.IsValidAddress(Address));
    }
}

}


tl;dr: Had the same problem, therefore built something that suits my (and hopefully your) needs: https://github.com/Sofoca/CoinUtils

My specific requirements were…

  • Support for Bitcoin and Litecoin (and in the future probably other similar Altcoins)
  • Support for all address types (P2SH and P2PKH) and encodings (Base58 and Bech32)
  • Preferably no external (think NuGet) dependencies

While none of the above mentioned alternatives met all those I took some inspiration from previous answers and referenced projects. Thanks for that!

Hope this helps those searching for a complete yet lightweight solution to this problem.

Tags:

C#

Bitcoin