Bitcoin/address validation

You are encouraged to solve this task according to the task description, using any language you may know.


Task

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   zero
   O   uppercase oh
   I   uppercase eye
   l   lowercase ell


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.



Example of a bitcoin address



1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i



It doesn't belong to anyone and is part of the test suite of the bitcoin software.

You can change a few characters in this string and check that it'll fail the test.




 
uses
  DCPsha256;
 
type
  TByteArray = array of Byte;
 
function HashSHA256(const Input: TByteArray): TByteArray;
var
  Hasher: TDCP_sha256;
begin
  Hasher := TDCP_sha256.Create(nil);
  try
    Hasher.Init;
    Hasher.Update(Input[0], Length(Input));
    SetLength(Result, Hasher.HashSize div 8);
    Hasher.Final(Result[0]);
  finally
    Hasher.Free;
  end;
end;
 
function DecodeBase58(const Input: string): TByteArray;
const
  Size = 25;
  Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
var
  C: Char;
  I, J: Integer;
begin
  SetLength(Result, Size);
 
  for C in Input do
  begin
    I := Pos(C, Alphabet) - 1;
 
    if I = -1 then
      raise Exception.CreateFmt('Invalid character found: %s', [C]);
 
    for J := High(Result) downto 0 do
    begin
      I := I + (58 * Result[J]);
      Result[J] := I mod 256;
      I := I div 256;
    end;
 
    if I <> 0 then
      raise Exception.Create('Address too long');
  end;
end;
 
procedure ValidateBitcoinAddress(const Address: string);
var
  Hashed: TByteArray;
  Decoded: TByteArray;
begin
  if (Length(Address) < 26) or (Length(Address) > 35) then
    raise Exception.Create('Wrong length');
 
  Decoded := DecodeBase58(Address);
  Hashed := HashSHA256(HashSHA256(Copy(Decoded, 0, 21)));
 
  if not CompareMem(@Decoded[21], @Hashed[0], 4) then
    raise Exception.Create('Bad digest');
end;
 