Bitcoin/address validation
You are encouraged to solve this task according to the task description, using any language you may know.
Write a program that takes a bitcoin address as argument, and checks whether or not this address is valid.
A bitcoin address uses a base58 encoding, which uses an alphabet of the characters 0 .. 9, A ..Z, a .. z, but without the four characters 0, O, I and l.
With this encoding, a bitcoin address encodes 25 bytes:
- the first byte is the version number, which will be zero for this task ;
- the next twenty bytes are a RIPEMD-160 digest, but you don't have to know that for this task: you can consider them a pure arbitrary data ;
- the last four bytes are a checksum check. They are the first four bytes of a double SHA-256 digest of the previous 21 bytes.
To check the bitcoin address, you must read the first twenty-one bytes, compute the checksum, and check that it corresponds to the last four bytes.
The program can either return a boolean value or throw an exception when not valid.
You can use a digest library for SHA-256.
Here is an example of a bitcoin address:
1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i
It does not belong to anyone. It is part of the test suite of the bitcoin software. You can change a few characters in this string and check that it will fail the test.
extra credit: allow your code to deal with compressed keys
Ada
<lang ada>with Ada.Exceptions, Interfaces; with Ada.Streams; use Ada.Exceptions, Interfaces; use Ada.Streams;
package Bitcoin is
subtype BT_Raw_Addr is Stream_Element_Array(1..25); subtype BT_Checksum is Stream_Element_Array(1..4); subtype BT_Addr is String(1..34); subtype Sha256String is String(1..64); Invalid_Address_Error : Exception;
function Double_Sha256(S : Stream_Element_Array) return BT_Checksum; function Is_Valid(A : BT_Raw_Addr) return Boolean; procedure Base58_Decode(S : BT_Addr; A : out BT_Raw_Addr) ;
private
Base58 : constant String := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; function Hex_Val (C, C2 : Character) return Stream_Element;
end Bitcoin;
with GNAT.SHA256, Ada.Strings.Fixed;
use GNAT.SHA256, Ada.Strings.Fixed;
package body Bitcoin is
function Hex_Val (C, C2 : Character) return Stream_Element is
subtype Nibble is Integer range 0..15; HEX : array (0..255) of Nibble := ( 48=>0, 49=>1, 50=>2, 51=>3, 52=>4, 53=>5, 54=>6, 55=>7, 56=>8, 57=>9 , 65=>10, 66=>11, 67=>12, 68 =>13, 69 =>14, 70 =>15 , 97=>10, 98=>11, 99=>12, 100=>13, 101=>14, 102=>15 , Others=>0 );
begin
return Stream_Element(HEX(Character'Pos(C)) * 16 + HEX(Character'Pos(C2)));
end Hex_Val;
function Double_Sha256(S : Stream_Element_Array) return BT_Checksum is
Ctx : Context := Initial_Context; D : Message_Digest; S2 : Stream_Element_Array(1..32); Ctx2 : Context := Initial_Context; C : BT_Checksum;
begin
Update(Ctx, S); D := Digest(Ctx); for I in S2'Range loop S2(I) := Hex_Val(D(Integer(I)*2-1), D(Integer(I)*2)); end loop; Update(Ctx2, S2); D := Digest(Ctx2); for I in C'Range loop C(I) := Hex_Val(D(Integer(I)*2-1), D(Integer(I)*2)); end loop; return C;
end Double_Sha256;
-- Summary of Base58: -- -- We decode S into a 200 bit unsigned integer. -- -- We could use a BigNum library, but choose to go without. --
procedure Base58_Decode(S : BT_Addr; A : out BT_Raw_Addr) is begin
A := (Others => 0); for I in S'Range loop declare P : Natural := Index(Base58, String(S(I..I))); C : Natural; begin if P = 0 then raise Invalid_Address_Error; end if; C := P - 1; for J in reverse A'Range loop C := C + Natural(A(J)) * 58; A(J) := Stream_Element(Unsigned_32(C) and 255); -- 0x00FF C := Natural(Shift_Right(Unsigned_32(C),8) and 255); -- 0xFF00 end loop; if C /= 0 then raise Invalid_Address_Error; end if; end; end loop;
end Base58_Decode;
function Is_Valid(A : BT_Raw_Addr) return Boolean is
begin
return A(1) = 0 and A(22..25) = Double_Sha256(A(1..21));
end Is_Valid;
end Bitcoin;
with Ada.Text_IO, Bitcoin; use Ada.Text_IO, Bitcoin;
procedure Bitcoin_Addr_Validate is begin
declare BTs : array (positive range <>) of BT_Addr := ( "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- VALID , "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9" -- VALID , "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X" -- checksum changed, original data. , "1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- data changed, original checksum. , "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- invalid chars ); begin for I in Bts'Range loop declare A : BT_Raw_Addr; Valid : Boolean; begin Put(BTs(I) & " validity: "); Base58_Decode(BTs(I), A); Valid := Is_Valid(A); Put_Line(Boolean'Image(Valid)); exception when E : Invalid_Address_Error => Put_Line ("*** Error: Invalid BT address."); end; end loop; end;
end Bitcoin_Addr_Validate; </lang>
- Output:
1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i validity: TRUE 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9 validity: TRUE 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X validity: FALSE 1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i validity: FALSE 1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i validity: *** Error: Invalid BT address.
C
<lang c>#include <stdio.h>
- include <string.h>
- include <openssl/sha.h>
const char *coin_err;
- define bail(s) { coin_err = s; return 0; }
int unbase58(const char *s, unsigned char *out) { static const char *tmpl = "123456789" "ABCDEFGHJKLMNPQRSTUVWXYZ" "abcdefghijkmnopqrstuvwxyz"; int i, j, c; const char *p;
memset(out, 0, 25); for (i = 0; s[i]; i++) { if (!(p = strchr(tmpl, s[i]))) bail("bad char");
c = p - tmpl; for (j = 25; j--; ) { c += 58 * out[j]; out[j] = c % 256; c /= 256; }
if (c) bail("address too long"); }
return 1; }
int valid(const char *s) { unsigned char dec[32], d1[SHA256_DIGEST_LENGTH], d2[SHA256_DIGEST_LENGTH];
coin_err = ""; if (!unbase58(s, dec)) return 0;
SHA256(SHA256(dec, 21, d1), SHA256_DIGEST_LENGTH, d2);
if (memcmp(dec + 21, d2, 4)) bail("bad digest");
return 1; }
int main (void) { const char *s[] = { "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I", 0 }; int i; for (i = 0; s[i]; i++) { int status = valid(s[i]); printf("%s: %s\n", s[i], status ? "Ok" : coin_err); }
return 0; }</lang> Compile with -lcrypto
- Output:
1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9: Ok 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i: Ok 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9: bad digest 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I: bad char
C#
This requires NUnit package to compile. <lang csharp> using System; using System.Linq; using System.Security.Cryptography; using NUnit.Framework;
namespace BitcoinValidator {
public class ValidateTest { [TestCase] public void ValidateBitcoinAddressTest() { Assert.IsTrue(ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i")); // VALID Assert.IsTrue(ValidateBitcoinAddress("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9")); // VALID Assert.Throws<Exception>(() => ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X")); // checksum changed, original data Assert.Throws<Exception>(() => ValidateBitcoinAddress("1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i")); // data changed, original checksum Assert.Throws<Exception>(() => ValidateBitcoinAddress("1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i")); // invalid chars Assert.Throws<Exception>(() => ValidateBitcoinAddress("BZbvjr")); // checksum is fine, address too short }
public static bool ValidateBitcoinAddress(string address) { if (address.Length < 26 || address.Length > 35) throw new Exception("wrong length"); var decoded = DecodeBase58(address); var d1 = Hash(decoded.SubArray(0, 21)); var d2 = Hash(d1); if (!decoded.SubArray(21, 4).SequenceEqual(d2.SubArray(0, 4))) throw new Exception("bad digest"); return true; }
const string Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; const int Size = 25;
private static byte[] DecodeBase58(string input) { var output = new byte[Size]; foreach (var t in input) { var p = Alphabet.IndexOf(t); if (p == -1) throw new Exception("invalid character found"); var j = Size; while (--j > 0) { p += 58 * output[j]; output[j] = (byte)(p % 256); p /= 256; } if (p != 0) throw new Exception("address too long"); } return output; }
private static byte[] Hash(byte[] bytes) { var hasher = new SHA256Managed(); return hasher.ComputeHash(bytes); } }
public static class ArrayExtensions { public static T[] SubArray<T>(this T[] data, int index, int length) { var result = new T[length]; Array.Copy(data, index, result, 0, length); return result; } }
} </lang>
D
This requires the D module from the SHA-256 Task.
<lang d>import std.stdio, std.algorithm, std.array, std.string, sha_256_2;
struct A25 {
// Type for a 25 ubyte (not base58 encoded) bitcoin address. ubyte[25] enc;
ubyte bitcoinVersion() const pure nothrow @safe @nogc { return enc[0]; }
ubyte[4] embeddedChecksum() return const pure nothrow @safe @nogc { return enc[$ - 4 .. $]; }
/** Computes a double sha256 hash of the first 21 bytes of the address. Returns the full 32 ubyte sha256 hash. */ ubyte[32] doubleSHA256() const pure nothrow @nogc { return SHA256.digest(SHA256.digest(enc[0 .. 21])); }
/** Returns a four ubyte checksum computed from the first 21 bytes of the address. */ ubyte[4] computeChecksum() const pure nothrow @nogc { return doubleSHA256[0 .. 4]; }
/** Takes a base58 encoded address and decodes it into the receiver. Errors are returned if the argument is not valid base58 or if the decoded value does not fit in the 25 ubyte address. The address is not otherwise checked for validity. */ string set58(in ubyte[] s) pure nothrow @safe @nogc { static immutable digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" .representation; static assert(digits.length == 58);
foreach (immutable char s1; s) { immutable c = digits.countUntil(s1); if (c < 0) return "found a bad char in the Bitcoin address.";
// Currently the D type system can't see c as nonegative. uint uc = (c < 0) ? 0 : c;
foreach_reverse (ref aj; enc) { uc += digits.length * aj; aj = uc % 256; uc /= 256; } if (uc > 0) return "too long Bitcoin address."; }
return null; }
}
/** Validates a base58 encoded bitcoin address. An address is valid if it can be decoded into a 25 ubyte address, the Version number is 0, and the checksum validates. Return value ok will be true for valid addresses. If ok is false, the address is invalid and the error value may indicate why. */ string isValidA58(in ubyte[] a58) pure nothrow @nogc {
A25 a; immutable err = a.set58(a58); if (!err.empty) return err; if (a.bitcoinVersion != 0) return "not Bitcoin version 0."; return (a.embeddedChecksum == a.computeChecksum) ? null : "checksums don't match.";
}
void main() {
immutable tests = ["1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62j", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62!", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62iz", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz"];
foreach (immutable test; tests) { immutable err = test.representation.isValidA58; writefln(`"%s": %s`, test, err.empty ? "OK." : err); }
}</lang>
- Output:
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i": OK. "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62j": checksums don't match. "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62!": found a bad char in the Bitcoin address. "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62iz": not Bitcoin version 0. "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz": too long Bitcoin address.
Erlang
Using base58 module from http://github.com/titan098/erl-base58.git.
<lang Erlang> -module( bitcoin_address ).
-export( [task/0, validate/1] ).
task() -> io:fwrite( "Validate ~p~n", ["1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"] ), io:fwrite( "~p~n", [validate("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i")] ), io:fwrite( "Validate ~p~n", ["1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW622"] ), io:fwrite( "~p~n", [validate("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW622")] ).
validate( String ) -> {length_25, <<Address:21/binary, Checksum:4/binary>>} = {length_25, base58:base58_to_binary( String )}, <<Version:1/binary, _/binary>> = Address, {version_0, <<0>>} = {version_0, Version}, <<Four_bytes:4/binary, _T/binary>> = crypto:hash( sha256, crypto:hash(sha256, Address) ), {checksum, Checksum} = {checksum, Four_bytes}, ok. </lang>
- Output:
17> bitcoin_address:task(). Validate "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" ok Validate "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW622" ** exception error: no match of right hand side value {checksum,<<"ÀF²ÿ">>} in function bitcoin_address:validate/1 (src/bitcoin_address.erl, line 16) in call from bitcoin_address:task/0 (src/bitcoin_address.erl, line 9)
Go
<lang go>package main
import (
"bytes" "crypto/sha256" "errors" "os"
)
// With at least one other bitcoin RC task, this source is styled more like // a package to show how functions of the two tasks might be combined into // a single package. It turns out there's not really that much shared code, // just the A25 type and doubleSHA256 method, but it's enough to suggest how // the code might be organized. Types, methods, and functions are capitalized // where they might be exported from a package.
// A25 is a type for a 25 byte (not base58 encoded) bitcoin address. type A25 [25]byte
func (a *A25) Version() byte {
return a[0]
}
func (a *A25) EmbeddedChecksum() (c [4]byte) {
copy(c[:], a[21:]) return
}
// DoubleSHA256 computes a double sha256 hash of the first 21 bytes of the // address. This is the one function shared with the other bitcoin RC task. // Returned is the full 32 byte sha256 hash. (The bitcoin checksum will be // the first four bytes of the slice.) func (a *A25) doubleSHA256() []byte {
h := sha256.New() h.Write(a[:21]) d := h.Sum([]byte{}) h = sha256.New() h.Write(d) return h.Sum(d[:0])
}
// ComputeChecksum returns a four byte checksum computed from the first 21 // bytes of the address. The embedded checksum is not updated. func (a *A25) ComputeChecksum() (c [4]byte) {
copy(c[:], a.doubleSHA256()) return
}/* Go */
// Tmpl and Set58 are adapted from the C solution. // Go has big integers but this techinique seems better. var tmpl = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
// Set58 takes a base58 encoded address and decodes it into the receiver. // Errors are returned if the argument is not valid base58 or if the decoded // value does not fit in the 25 byte address. The address is not otherwise // checked for validity. func (a *A25) Set58(s []byte) error {
for _, s1 := range s { c := bytes.IndexByte(tmpl, s1) if c < 0 { return errors.New("bad char") } for j := 24; j >= 0; j-- { c += 58 * int(a[j]) a[j] = byte(c % 256) c /= 256 } if c > 0 { return errors.New("too long") } } return nil
}
// ValidA58 validates a base58 encoded bitcoin address. An address is valid // if it can be decoded into a 25 byte address, the version number is 0, // and the checksum validates. Return value ok will be true for valid // addresses. If ok is false, the address is invalid and the error value // may indicate why. func ValidA58(a58 []byte) (ok bool, err error) {
var a A25 if err := a.Set58(a58); err != nil { return false, err } if a.Version() != 0 { return false, errors.New("not version 0") } return a.EmbeddedChecksum() == a.ComputeChecksum(), nil
}
// Program returns exit code 0 with valid address and produces no output. // Otherwise exit code is 1 and a message is written to stderr. func main() {
if len(os.Args) != 2 { errorExit("Usage: valid <base58 address>") } switch ok, err := ValidA58([]byte(os.Args[1])); { case ok: case err == nil: errorExit("Invalid") default: errorExit(err.Error()) }
}
func errorExit(m string) {
os.Stderr.WriteString(m + "\n") os.Exit(1)
}</lang>
- Output:
Command line usage examples showing program exit status.
> valid ; echo $status Usage: valid <base58 address> 1 > valid 1 1 ; echo $status Usage: valid <base58 address> 1 > valid 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i ; echo $status 0 > valid 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62j ; echo $status Invalid 1 > valid 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62! ; echo $status bad char 1 > valid 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62iz ; echo $status not version 0 1 > valid 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz ; echo $status too long 1
Haskell
<lang haskell>import Data.List (unfoldr, elemIndex) import Data.Binary (Word8) import Crypto.Hash.SHA256 (hash) import Data.ByteString (unpack, pack)
-- Convert from base58 encoded value to Integer decode58 :: String -> Maybe Integer decode58 = foldl (\v d -> (+) <$> ((58*) <$> v) <*> (fromIntegral <$> elemIndex d c58 )) $ Just 0
where c58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
-- Convert from base58 encoded value to list of bytes toBytes :: Integer -> [Word8] toBytes x = reverse $ unfoldr (\b -> if b == 0 then Nothing else Just (fromIntegral $ b `mod` 256, b `div` 256)) x
-- Check validity of base58 encoded bitcoin address. -- Result is either an error string (Left) or a validity bool (Right). validityCheck :: String -> Either String Bool validityCheck encodedAddress =
let d58 = decode58 encodedAddress in case d58 of Nothing -> Left "Invalid base 58 encoding" Just ev -> let address = toBytes ev addressLength = length address in if addressLength > 24 then Left "Address length exceeds 25 bytes" else if addressLength < 4 then Left "Address length less than 4 bytes" else let (bs,cs) = splitAt 21 $ replicate (25 - addressLength) 0 ++ address in Right $ all (uncurry (==)) (zip cs $ unpack $ hash $ hash $ pack bs)
-- Run one validity check and display results. validate :: String -> IO () validate encodedAddress =
let vc = validityCheck encodedAddress in case vc of Left err -> putStrLn $ show encodedAddress ++ " -> " ++ show err Right validity -> putStrLn $ show encodedAddress ++ " -> " ++ if validity then "Valid" else "Invalid"
-- Run some validity check tests. main :: IO () main = do
validate "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- VALID validate "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9" -- VALID validate "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X" -- checksum changed, original data. validate "1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- data changed, original checksum. validate "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- invalid chars validate "1ANa55215ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -- too long validate "i55j" -- too short </lang>
- Output:
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -> Valid "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9" -> Valid "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X" -> Invalid "1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -> Invalid "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -> "Invalid base 58 encoding" "1ANa55215ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" -> "Address length exceeds 25 bytes" "i55j" -> "Address length less than 4 bytes"
Mathematica / Wolfram Language
<lang Mathematica>chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; data =
IntegerDigits[ FromDigits[ StringPosition[chars, #]1 - 1 & /@ Characters[InputString[]], 58], 256, 25];
data-4 ;; ==
IntegerDigits[ Hash[FromCharacterCode[ IntegerDigits[Hash[FromCharacterCode[data;; -5], "SHA256"], 256, 32]], "SHA256"], 256, 32];; 4</lang>
- Input:
1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i 2AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i
- Output:
True False
Java
<lang java>private final static String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public static void main(String[] args) {
Assert("Test 01", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"), true); Assert("Test 02", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62j"), false); Assert("Test 03", ValidateBitcoinAddress("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9"), true); Assert("Test 04", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X"), false); Assert("Test 05", ValidateBitcoinAddress("1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"), false); Assert("Test 06", ValidateBitcoinAddress("1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"), false); Assert("Test 07", ValidateBitcoinAddress("BZbvjr"), false); Assert("Test 08", ValidateBitcoinAddress("i55j"), false); Assert("Test 09", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62!"), false); Assert("Test 10", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62iz"), false); Assert("Test 11", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz"), false); Assert("Test 12", ValidateBitcoinAddress("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9"), false); Assert("Test 13", ValidateBitcoinAddress("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I"), false);
}
public static boolean ValidateBitcoinAddress(String addr) {
if (addr.length() < 26 || addr.length() > 35) return false; byte[] decoded = DecodeBase58(addr, 58, 25); if (decoded == null) return false;
byte[] hash = Sha256(decoded, 0, 21, 2);
return Arrays.equals(Arrays.copyOfRange(hash, 0, 4), Arrays.copyOfRange(decoded, 21, 25));
}
private static byte[] DecodeBase58(String input, int base, int len) {
byte[] output = new byte[len]; for (int i = 0; i < input.length(); i++) { char t = input.charAt(i);
int p = ALPHABET.indexOf(t); if (p == -1) return null; for (int j = len - 1; j > 0; j--, p /= 256) { p += base * (output[j] & 0xFF); output[j] = (byte) (p % 256); } if (p != 0) return null; }
return output;
}
private static byte[] Sha256(byte[] data, int start, int len, int recursion) {
if (recursion == 0) return data;
try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(Arrays.copyOfRange(data, start, start + len)); return Sha256(md.digest(), 0, 32, recursion - 1); } catch (NoSuchAlgorithmException e) { return null; }
}
public static void Assert(String name, boolean value, boolean expected) {
if (value ^ expected) throw new Error("Test " + name + " failed"); else System.out.println(name + " passed");
}</lang>
Oberon-2
<lang oberon2> MODULE BitcoinAddress; IMPORT
Object, NPCT:Tools, Crypto:SHA256, S := SYSTEM, Out;
CONST
BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
TYPE
BC_RAW = ARRAY 25 OF CHAR; SHA256_HASH = ARRAY 32 OF CHAR;
VAR
b58: Object.CharsLatin1;
PROCEDURE IndexOfB58Char(c: CHAR): INTEGER; VAR
i: INTEGER;
BEGIN
i := 0; WHILE (b58[i] # 0X) & (b58[i] # c) DO INC(i) END; IF b58[i] = 0X THEN RETURN -1 ELSE RETURN i END
END IndexOfB58Char;
PROCEDURE DecodeB58(s [NO_COPY]: ARRAY OF CHAR;VAR out: BC_RAW): BOOLEAN; VAR
i,j,k: LONGINT;
BEGIN
FOR i := 0 TO LEN(out) - 1 DO; out[i] := CHR(0) END; i := 0; WHILE (s[i] # 0X) DO; k := IndexOfB58Char(s[i]); IF k < 0 THEN Out.String("Error: Bad base58 character");Out.Ln; RETURN FALSE END; FOR j := LEN(out) - 1 TO 0 BY -1 DO k := k + 58 * ORD(out[j]); out[j] := CHR(k MOD 256); k := k DIV 256; END;
IF k # 0 THEN Out.String("Error: Address to long");Out.Ln; RETURN FALSE END; INC(i) END; RETURN TRUE
END DecodeB58;
PROCEDURE Valid(s [NO_COPY]: ARRAY OF CHAR): BOOLEAN; VAR
dec: BC_RAW; d1, d2: SHA256.Hash; d1Str, d2Str: SHA256_HASH; x,y: LONGINT;
BEGIN
Out.String(s);Out.String(" is valid? "); IF ~DecodeB58(s,dec) THEN RETURN FALSE END;
d1 := SHA256.NewHash();d1.Initialize(); d2 := SHA256.NewHash();d2.Initialize(); d1.Update(dec,0,21);d1.GetHash(d1Str,0); d2.Update(d1Str,0,d1.size);d2.GetHash(d2Str,0); S.MOVE(S.ADR(dec) + 21,S.ADR(x),4); S.MOVE(S.ADR(d2Str),S.ADR(y),4); RETURN (x = y)
END Valid;
BEGIN
b58 := Tools.AsString(BASE58); Out.Bool(Valid("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9"));Out.Ln; Out.Bool(Valid("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i"));Out.Ln; Out.Bool(Valid("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9"));Out.Ln; Out.Bool(Valid("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I"));Out.Ln
END BitcoinAddress. </lang>
- Output:
1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9 is valid? TRUE 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i is valid? TRUE 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9 is valid? FALSE 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I is valid? Error: Bad base58 character FALSE
Perl
<lang perl>my @b58 = qw{
1 2 3 4 5 6 7 8 9 A B C D E F G H J K L M N P Q R S T U V W X Y Z a b c d e f g h i j k m n o p q r s t u v w x y z
}; my %b58 = map { $b58[$_] => $_ } 0 .. 57;
sub unbase58 {
use integer; my @out; my $azeroes = length($1) if $_[0] =~ /^(1*)/; for my $c ( map { $b58{$_} } $_[0] =~ /./g ) { for (my $j = 25; $j--; ) { $c += 58 * ($out[$j] // 0); $out[$j] = $c % 256; $c /= 256; } } my $bzeroes = length($1) if join(, @out) =~ /^(0*)/; die "not a 25 byte address\n" if $bzeroes != $azeroes; return @out;
}
sub check_bitcoin_address {
# Does nothing if address is valid # dies otherwise use Digest::SHA qw(sha256); my @byte = unbase58 shift; die "wrong checksum\n" unless (pack 'C*', @byte[21..24]) eq substr sha256(sha256 pack 'C*', @byte[0..20]), 0, 4;
}</lang>
Perl 6
<lang perl6>my regex bitcoin-address {
<+alnum-[0IOl]> ** 26..* # an address is at least 26 characters long <?{ use Digest::SHA; .subbuf(21, 4) eqv sha256(sha256 .subbuf(0, 21)).subbuf(0, 4) given Blob.new: < 1 2 3 4 5 6 7 8 9 A B C D E F G H J K L M N P Q R S T U V W X Y Z a b c d e f g h i j k m n o p q r s t u v w x y z >.pairs.invert.hash{$/.comb} .reduce(* * 58 + *) .polymod(256 xx 24) .reverse; }>
}
say "Here is a bitcoin address: 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i" ~~ /<bitcoin-address>/;</lang>
- Output:
「1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i」 bitcoin-address => 「1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i」
PHP
<lang php> function validate($address){
$decoded = decodeBase58($address);
$d1 = hash("sha256", substr($decoded,0,21), true); $d2 = hash("sha256", $d1, true);
if(substr_compare($decoded, $d2, 21, 4)){ throw new \Exception("bad digest"); } return true;
} function decodeBase58($input) {
$alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
$out = array_fill(0, 25, 0); for($i=0;$i<strlen($input);$i++){ if(($p=strpos($alphabet, $input[$i]))===false){ throw new \Exception("invalid character found"); } $c = $p; for ($j = 25; $j--; ) { $c += (int)(58 * $out[$j]); $out[$j] = (int)($c % 256); $c /= 256; $c = (int)$c; } if($c != 0){ throw new \Exception("address too long"); } }
$result = ""; foreach($out as $val){ $result .= chr($val); }
return $result;
}
function main () {
$s = array( "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I", ); foreach($s as $btc){ $message = "OK"; try{ validate($btc); }catch(\Exception $e){ $message = $e->getMessage(); } echo "$btc: $message\n"; }
}
main();
</lang>
- Output:
1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9: OK 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i: OK 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nJ9: bad digest 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62I: invalid character found
PicoLisp
<lang PicoLisp>(setq *Alphabet
(chop "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"))
- if returns NIL then adress is already invalid
(de base58 (Str)
(let N 0 (for L (chop Str) (setq N (+ (* N 58) (index L *Alphabet) -1 ) ) ) N )
)
(de sha256 (Lst)
(native "libcrypto.so" "SHA256" '(B . 32) (cons NIL (32) (native "libcrypto.so" "SHA256" '(B . 32) (cons NIL (32) Lst) (length Lst) '(NIL (32))) ) 32 '(NIL (32)) ) )
(de bytes25 (N)
(flip (make (do 25 (link (% N 256)) (setq N (/ N 256)) ) ) ) )
(de valid (Str)
(and (base58 Str) (bytes25 @) (= (head 4 (sha256 (head 21 @))) (tail 4 @) ) ) )
(bye)</lang>
Python
<lang python>from hashlib import sha256
digits58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def decode_base58(bc, length):
n = 0 for char in bc: n = n * 58 + digits58.index(char) return n.to_bytes(length, 'big')
def check_bc(bc):
bcbytes = decode_base58(bc, 25) return bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4]
print(check_bc('1AGNa15ZQXAZUgFiqJ3i7Z2DPU2J6hW62i')) print(check_bc("17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j"))</lang>
- Output:
Returns:
- False
- True
- Help
- Yuuki-chan edit: Delete this help if it's not needed anymore
- For those looking at examples here to try and work out what is required, the
n.to_bytes()
call is equivalent to this code which converts a (long) integer into individual bytes of a byte array in a particular order: - <lang python>>>> n = 2491969579123783355964723219455906992268673266682165637887
>>> length = 25 >>> list( reversed(range(length)) ) [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] >>> assert n.to_bytes(length, 'big') == bytes( (n >> i*8) & 0xff for i in reversed(range(length))) >>> </lang>
Racket
<lang racket>
- lang racket/base
- Same sha-256 interface as the same-named task
(require ffi/unsafe ffi/unsafe/define openssl/libcrypto) (define-ffi-definer defcrypto libcrypto) (defcrypto SHA256_Init (_fun _pointer -> _int)) (defcrypto SHA256_Update (_fun _pointer _pointer _long -> _int)) (defcrypto SHA256_Final (_fun _pointer _pointer -> _int)) (define (sha256 bytes)
(define ctx (malloc 128)) (define result (make-bytes 32)) (SHA256_Init ctx) (SHA256_Update ctx bytes (bytes-length bytes)) (SHA256_Final result ctx) result)
- base58 decoding
(define base58-digits
(let ([v (make-vector 128 #f)]) (for ([i (in-naturals)] [c "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"]) (vector-set! v (char->integer c) i)) v))
(define (base58->integer str)
(for/fold ([n 0]) ([c str]) (+ (* n 58) (vector-ref base58-digits (char->integer c)))))
(define (int->bytes n digits)
(list->bytes (let loop ([n n] [digits digits] [acc '()]) (if (zero? digits) acc (let-values ([(q r) (quotient/remainder n 256)]) (loop q (sub1 digits) (cons r acc)))))))
(define (validate-bitcoin-address str)
(define bs (int->bytes (base58->integer str) 25)) (equal? (subbytes (sha256 (sha256 (subbytes bs 0 21))) 0 4) (subbytes bs 21)))
- additional tests taken from the other solutions
(validate-bitcoin-address "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i") ; => #t (validate-bitcoin-address "1111111111111111111114oLvT2") ; => #t (validate-bitcoin-address "17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j") ; => #t (validate-bitcoin-address "1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9") ; => #t (validate-bitcoin-address "1badbadbadbadbadbadbadbadbadbadbad") ; => #f </lang>
Ruby
<lang ruby>
- Validate Bitcoin address
- Nigel_Galloway
- October 13th., 2014
require 'digest/sha2' def convert g
i,e = ,[] (0...g.length/2).each{|n| e[n] = g[n+=n]+g[n+1]; i+='H2'} e.pack(i)
end N = [0,1,2,3,4,5,6,7,8,nil,nil,nil,nil,nil,nil,nil,9,10,11,12,13,14,15,16,nil,17,18,19,20,21,nil,22,23,24,25,26,27,28,29,30,31,32,nil,nil,nil,nil,nil,nil,33,34,35,36,37,38,39,40,41,42,43,nil,44,45,46,47,48,49,50,51,52,53,54,55,56,57] A = '1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62x' g = A.bytes.inject(0){|g,n| g*58+N[n-49]}.to_s(16) # A small and interesting piece of code to do the decoding of base58-encoded data. n = g.slice!(0..-9) (n.length...42).each{n.insert(0,'0')} puts "I think the checksum should be #{g}\nI calculate that it is #{Digest::SHA256.hexdigest(Digest::SHA256.digest(convert(n)))[0,8]}" </lang>
- Output:
With A = '1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'
I think the checksum should be c046b2ff I calculate that it is c046b2ff
With A = '1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62x' (final digit i corrupted to x).
I think the checksum should be c046b30d I calculate that it is c046b2ff
Tcl
<lang tcl>package require sha256
- Generate a large and boring piece of code to do the decoding of
- base58-encoded data.
apply {{} {
set chars "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" set i -1 foreach c [split $chars ""] {
lappend map $c "return -level 0 [incr i]"
} lappend map default {return -code error "bad character \"$c\""} proc base58decode str [string map [list @BODY@ [list $map]] {
set num 0 set count [expr {ceil(log(58**[string length $str])/log(256))}] foreach c [split $str {}] { set num [expr {$num*58+[switch $c @BODY@]}] } for {set i 0} {$i < $count} {incr i} { append result [binary format c [expr {$num & 255}]] set num [expr {$num >> 8}] } return [string reverse $result]
}]
}}
- How to check bitcoin address validity
proc bitcoin_addressValid {address} {
set a [base58decode $address] set ck [sha2::sha256 -bin [sha2::sha256 -bin [string range $a 0 end-4]]] if {[string range $a end-3 end] ne [string range $ck 0 3]} {
return -code error "signature does not match"
} return "$address is ok"
}</lang> Testing if it works <lang tcl>puts [bitcoin_addressValid 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9] puts [bitcoin_addressValid 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i]</lang>
- Output:
1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9 is ok 1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i is ok
UNIX Shell
<lang bash>base58=({1..9} {A..H} {J..N} {P..Z} {a..k} {m..z}) bitcoinregex="^[$(printf "%s" "${base58[@]}")]{34}$"
decodeBase58() {
local s=$1 for i in {0..57} do s="${s//${base58[i]}/ $i}" done dc <<< "16o0d${s// /+58*}+f"
}
checksum() {
xxd -p -r <<<"$1" | openssl dgst -sha256 -binary | openssl dgst -sha256 -binary | xxd -p -c 80 | head -c 8
}
checkBitcoinAddress() {
if "$1" =~ $bitcoinregex then h=$(decodeBase58 "$1") checksum "00${h::${#h}-8}" | grep -qi "^${h: -8}$" else return 2 fi
}</lang>
zkl
Uses shared library zklMsgHash. <lang zkl>var MsgHash=Import("zklMsgHash"); // SHA-256, etc const symbols="123456789" // 58 characters: no cap i,o; ell, zero "ABCDEFGHJKLMNPQRSTUVWXYZ" "abcdefghijkmnopqrstuvwxyz";
fcn unbase58(str){ // --> Data (byte bucket)
out:=(0).pump(25,Data,0); // 25 zero bytes str.pump(Void,symbols.index,'wrap(n){ // each chr of str [24..0,-1].reduce('wrap(c,idx){ // churn it and store result in Data (==out) c+=58*out[idx]; out[idx]=c; c/256; },n) : if(_) throw(Exception.ValueError("address too long")); }); out;
}
fcn coinValide(addr){
reg dec; try{ dec=unbase58(addr) }catch{ return(False) } // hash twice, once each time --> binary hash (instead of hex string) d1:=MsgHash.SHA256(MsgHash.SHA256(dec[0,21],1,False),1,False); d1[0,4]==dec[-4,*]; // two byte blobs
}</lang> <lang zkl>T("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i","1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9",
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62X", // checksum changed, original data. "1ANNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", // data changed, original checksum. "1A Na15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", // invalid chars "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62izz", // too long
).apply(coinValide).println();</lang>
- Output:
L(True,True,False,False,False,False)