Bitcoin uses a specific encoding format to encode the digest of an elliptic curve public point into a short ASCII string. The purpose of this task is to perform such a conversion.

The encoding steps are:

 take the X and Y coordinates of the given public point, and concatenate them in order to have a 64 byte-longed string ;
 add one byte prefix equal to 4 (it is a convention for this way of encoding a public point) ;
 compute the SHA-256 of this string ;
 compute the RIPEMD-160 of this SHA-256 digest ;
 compute the checksum of the concatenation of the version number digit (a single zero byte) and this RIPEMD-160 digest, as described in bitcoin/address validation ;
 Base-58 encode (see below) the concatenation of the version number (zero in this case), the ripemd digest and the checksum
The base-58 encoding is based on an alphabet of alphanumeric characters (numbers, upper case and lower case, in that order) but without the four characters 0, O, l and I.

Here is an example public point:

X = 0x50863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352
Y = 0x2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6
The corresponding address should be:
16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM

Nb. The leading '1' is not significant as 1 is zero in base-58. It is however often added to the bitcoin address for various reasons. There can actually be several of them. You can ignore this and output an address without the leading 1.

Extra credit: add a verification procedure about the public point, making sure it belongs to the secp256k1 elliptic curve

 
program Public_point_to_address;
 
{$APPTYPE CONSOLE}
 
uses
  System.SysUtils,
  Winapi.Windows,
  DCPsha256,
  DCPripemd160;
 
const
  bitcoinVersion = 0;
 
type
  TByteArray = array of Byte;
 
  TA25 = TByteArray;
 
  TPPoint = record
    x, y: TByteArray;
    constructor SetHex(xi, yi: ansistring);
  end;
 
  TA25Helper = record helper for TA25
  public
    constructor Create(p: TPPoint);
    function DoubleSHA256(): TByteArray;
    procedure UpdateChecksum();
    procedure SetPoint(p: TPPoint);
    function ToBase58: Ansistring;
  end;
 
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 HashRipemd160(const Input: TByteArray): TByteArray;
var
  Hasher: TDCP_ripemd160;
begin
  Hasher := TDCP_ripemd160.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;
 
{ TPPoint }
 
constructor TPPoint.SetHex(xi, yi: ansistring);
 
  function StrToHexArray(value: Ansistring): TByteArray;
  var
    b: ansistring;
    h, i: integer;
  begin
    SetLength(Result, 32);
 
    for i := 0 to 31 do
    begin
      b := '$' + copy(value, i * 2 + 1, 2);
      if not TryStrToInt(b, h) then
        raise Exception.CreateFmt('Error in TPPoint.SetHex.StrToHexArray: Invalid hex string in position %d of "x"',
          [i * 2]);
      Result[i] := h;
    end;
  end;
 
begin
  if (Length(xi) <> 64) or (Length(yi) <> 64) then
    raise Exception.Create('Error in TPPoint.SetHex: Invalid hex string length');
 
  x := StrToHexArray(xi);
  y := StrToHexArray(yi);
end;
 
{ TA25Helper }
 
constructor TA25Helper.Create(p: TPPoint);
begin
  SetLength(self, 25);
  SetPoint(p);
end;
 
function TA25Helper.DoubleSHA256: TByteArray;
begin
  Result := HashSHA256(HashSHA256(copy(self, 0, 21)));
end;
 
procedure TA25Helper.SetPoint(p: TPPoint);
var
  c, s: TByteArray;
begin
  c := concat([4], p.x, p.y);
  s := HashSHA256(c);
 
  self := concat([self[0]], HashRipemd160(s));
  SetLength(self, 25);
  UpdateChecksum;
end;
 
function TA25Helper.ToBase58: Ansistring;
var
  c, i, n: Integer;
const
  Size = 34;
  Alphabet: Ansistring = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
begin
  SetLength(Result, Size);
  for n := Size - 1 downto 0 do
  begin
    c := 0;
    for i := 0 to 24 do
    begin
      c := c * 256 + Self[i];
      Self[i] := byte(c div 58);
      c := c mod 58;
    end;
    Result[n + 1] := Alphabet[c + 1];
  end;
 
  i := 2;
  while (i < Size) and (result[i] = '1') do
    inc(i);
 
  Result := copy(Result, i - 1, Size);
end;
 
procedure TA25Helper.UpdateChecksum;
begin
  CopyMemory(@self[21], @self.DoubleSHA256[0], 4);
end;
 
var
  p: TPPoint;
  a: TA25;
 
const
  x: Ansistring = '50863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352';
  y: Ansistring = '2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6';
 
begin
  p.SetHex(x, y);
 
  a := TA25.Create(p);
  writeln(a.ToBase58);
  readln;
end.