program TestCrSysAPI;
{
  Some tests for the Delphi/FreePascal interface to CryptoSys API
  $Id: TestCrSysAPI $
  Copyright (C) 2010-23 David Ireland, DI Management Services Pty Limited.
  All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
  Provided as is with no warranties. Use at your own risk.
  Last updated:
    $Date: 2023-04-14 03:07 $
    $Revision: 5.1.0 $
}

{$APPTYPE CONSOLE}
{$mode Delphi}
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}

uses
  SysUtils, diCryptoSys;

const
  CRLF = #13 + #10;

type
  // Define our own dynamic array type for `Array of Byte`
  TByteArr = Array of Byte;

// UTILITIES WE USE IN THE TESTS...

Procedure pr_hexbytes(msg : String; arrbytes : Array of Byte);
// Displays an array of bytes as a hex string
var
  i : Integer;
  nbytes : Integer;
begin
  Write(msg);
  nbytes := Length(arrbytes);
  For i := 0 to (nbytes - 1) do
    Write(IntToHex(arrbytes[i], 2));
  WriteLn;
end;

Function bytes_from_hex(strhex : AnsiString) : TByteArr;
var
  nbytes : Integer;
begin
  nbytes := Length(strhex) div 2;
  // Dimension the byte array to receive the output
  Result := NIL;
  SetLength(Result, nbytes);
  // Do the conversion
  CNV_BytesFromHexStr(Pointer(Result), nbytes, strhex);
end;

Function bytes_to_hex(bytes : TByteArr) : AnsiString;
var
  nchars : Integer;
  nbytes : Integer;
begin
  nbytes := Length(bytes);
  nchars := nbytes * 2;
  // Dimension the output
  SetLength(Result, nchars);
  // Do the conversion
  CNV_HexStrFromBytes(Pointer(Result), nchars, Pointer(bytes), nbytes);
end;

Function bytes_from_string(s : AnsiString) : TByteArr;
var
  i : Integer;
begin
  Result := NIL;
  SetLength(Result, Length(s));
  for i := 1 to Length(s) do
    Result[i-1] := Ord(s[i]);
end;

Function bytes_to_string(bytes : TByteArr) : AnsiString;
var
  i : Integer;
begin
  SetLength(Result, Length(bytes));
  for i := 1 to Length(bytes) do
    Result[i] := Chr(bytes[i-1]);
end;

Function bytes_copy(const Input : Array of Byte) : TByteArr;
begin
  Result := NIL;
  SetLength(Result, Length(Input));
  Move(input[0], Result[0], Length(Input));
end;

Procedure create_text_file(fname: String; str : String);
var
  myFile : TextFile;
begin
  AssignFile(myFile, fname);
  ReWrite(myFile);
  Write(myFile, str);
  CloseFile(myFile);
end;

const
  // Correct message digest test vectors for Hash('abc')
  OK_MD5_ABC    : String  = '900150983cd24fb0d6963f7d28e17f72';
  OK_SHA1_ABC   : String  = 'a9993e364706816aba3e25717850c26c9cd0d89d';
  OK_SHA256_ABC : String  = 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad';
  OK_SHA384_ABC : String  = 'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7';
  OK_SHA512_ABC : String  = 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f';
  OK_SHA224_ABC : String  = '23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7';
  OK_RMD160_ABC : String  = '8eb208f7e05d987a9b044a8e98c6b087f15a0bfc';

  // Byte arrays used to test GCM authenticated encryption
  // Test case 6
  gcm_key : Array[0..15] of Byte = (
    $FE, $FF, $E9, $92, $86, $65, $73, $1C,
    $6D, $6A, $8F, $94, $67, $30, $83, $08
  );
  gcm_pt  : Array[0..59] of Byte = (
    $D9, $31, $32, $25, $F8, $84, $06, $E5,
    $A5, $59, $09, $C5, $AF, $F5, $26, $9A,
    $86, $A7, $A9, $53, $15, $34, $F7, $DA,
    $2E, $4C, $30, $3D, $8A, $31, $8A, $72,
    $1C, $3C, $0C, $95, $95, $68, $09, $53,
    $2F, $CF, $0E, $24, $49, $A6, $B5, $25,
    $B1, $6A, $ED, $F5, $AA, $0D, $E6, $57,
    $BA, $63, $7B, $39
  );
  gcm_adata : Array[0..19] of Byte = (
    $FE, $ED, $FA, $CE, $DE, $AD, $BE, $EF,
    $FE, $ED, $FA, $CE, $DE, $AD, $BE, $EF,
    $AB, $AD, $DA, $D2
  );
  gcm_iv : Array[0..59] of Byte = (
    $93, $13, $22, $5D, $F8, $84, $06, $E5,
    $55, $90, $9C, $5A, $FF, $52, $69, $AA,
    $6A, $7A, $95, $38, $53, $4F, $7D, $A1,
    $E4, $C3, $03, $D2, $A3, $18, $A7, $28,
    $C3, $C0, $C9, $51, $56, $80, $95, $39,
    $FC, $F0, $E2, $42, $9A, $6B, $52, $54,
    $16, $AE, $DB, $F5, $A0, $DE, $6A, $57,
    $A6, $37, $B3, $9B
  );
  gcm_ok_ct : Array[0..59] of Byte = (
    $8C, $E2, $49, $98, $62, $56, $15, $B6,
    $03, $A0, $33, $AC, $A1, $3F, $B8, $94,
    $BE, $91, $12, $A5, $C3, $A2, $11, $A8,
    $BA, $26, $2A, $3C, $CA, $7E, $2C, $A7,
    $01, $E4, $A9, $A4, $FB, $A4, $3C, $90,
    $CC, $DC, $B2, $81, $D4, $8C, $7C, $6F,
    $D6, $28, $75, $D2, $AC, $A4, $17, $03,
    $4C, $34, $AE, $E5
  );
  gcm_ok_tag : Array[0..15] of Byte = (
    $61, $9C, $C5, $AE, $FF, $FE, $0B, $FA,
    $46, $2A, $F4, $3C, $16, $99, $D0, $50
  );


// DO SOME TESTS TO CALL CRYPTOSYS API FUNCTIONS
Procedure do_tests;
var
  strhex : AnsiString;
  arrbytes : TByteArr;
  nbytes : Integer;
  ret : Integer;
  s : AnsiString;
  buf : AnsiString;
  ch : Char;
  nchars : Integer;
  keyhex : AnsiString;
  ivhex : AnsiString;
  inputhex : AnsiString;
  outputhex : AnsiString;
  correcthex : ansiString;
  abKey : TByteArr;
  abIV  : TByteArr;
  abIn  : TByteArr;
  abOut : TByteArr;
  abCorrect : TByteArr;
  digesthex : AnsiString;
  fname : AnsiString;
  hContext : Integer;
  i : Integer;
  tag : Array[0..15] of Byte;
  isok : Boolean;
  abAAD : TByteArr;
  niter : Integer;
  pthex   : AnsiString;
  block : TByteArr;
  nextblk : TByteArr;
  lastblk : TByteArr;
  

begin
  WriteLn('Running ' + ExtractFileName(ParamStr(0)) + ' at ' + DateTimeToStr(Now));

  WriteLn(CRLF + 'GENERAL:');
  WriteLn('Interrogate the core diCryptoSys DLL:');
  WriteLn('API_Version='+(IntToStr(API_Version)));

  nchars := API_CompileTime(NIL, 0);
  WriteLn('API_CompileTime returns nchars=' + IntToStr(nchars));
  buf := AnsiString(StringOfChar(#0,nchars));
  nchars := API_CompileTime(Pointer(buf), nchars);
  WriteLn('API_CompileTime=' + buf);
  Assert (nchars > 0);
  ch := Chr(API_LicenceType(0));
  WriteLn('API_LicenceType='+ ch);
  nchars := API_ModuleName(NIL, 0, 0);
  WriteLn('API_ModuleName returns nchars=' + IntToStr(nchars));
  buf := AnsiString(StringOfChar(#0, nchars));
  API_ModuleName(Pointer(buf), nchars, 0);
  WriteLn('API_ModuleName=' + Trim(string(buf)));
  ret := API_PowerUpTests(0);
  WriteLn('API_PowerUpTests returns ' + IntToStr(ret) + ' (expected 0)');

  WriteLn(CRLF + 'HEX TO BYTE ARRAY CONVERSIONS:');
  // 1. Convert a hex string to a byte array
  strhex := 'deadbeef00010203';
  WriteLn('Hex= "' + strhex + '"');
  nbytes := Length(strhex) div 2;
  // Dimension the byte array to receive the output--IMPORTANT
  SetLength(arrbytes, nbytes);
  // Do the conversion
  ret := CNV_BytesFromHexStr(Pointer(arrbytes), nbytes, strhex);
  WriteLn('CNV_BytesFromHexStr returns ' + IntToStr(ret));
  pr_hexbytes('Bytes=', arrbytes);

  // Now convert this byte array back to a hex string
  nchars := nbytes * 2;
  // Dimension the string to receive the output--IMPORTANT
  s := AnsiString(StringOfChar(#0, nchars));
  ret := CNV_HexStrFromBytes(Pointer(s), nchars, Pointer(arrbytes), nbytes);
  WriteLn('CNV_HexStrFromBytes returns ' + IntToStr(ret));
  WriteLn('Str= "' + s + '"');

  WriteLn(CRLF + 'TEST AES128_HexMode:');
  keyhex := '0123456789ABCDEFF0E1D2C3B4A59687';
  ivhex := 'FEDCBA9876543210FEDCBA9876543210';
  // "Now is the time for all good men"
  inputhex   := '4E6F77206973207468652074696D6520666F7220616C6C20676F6F64206D656E';
  correcthex := 'C3153108A8DD340C0BCB1DFE8D25D2320EE0E66BD2BB4A313FB75C5638E9E177';
  // Set sOutput to be same length as sInput--IMPORTANT
  outputhex := AnsiString(StringOfChar(#0,Length(inputhex)));
  WriteLn('KY=' + keyhex);
  WriteLn('IV=' + ivhex);
  WriteLn('PT=' + inputhex);
  // Encrypt hex string in one-off process
  ret := AES128_HexMode(Pointer(outputhex), inputhex, keyhex, ENCRYPT, 'CBC', ivhex);
  WriteLn('CT=' + string(outputhex) + ' ret=' + IntToStr(ret));
  WriteLn('OK=' + correcthex);
  // Now decrypt back to plain text
  ret := AES128_HexMode(Pointer(outputhex), outputhex, keyhex, DECRYPT, 'cbc', ivhex);
  WriteLn('P''=' + string(outputhex) + ' ret=' + IntToStr(ret));
  Assert(AnsiCompareStr(string(outputhex), string(inputhex))= 0);

  WriteLn(CRLF + 'TEST AES128_BytesMode:');
  // NIST SP800-38a F.2.1 CBC-AES128.Encrypt
  abKey := bytes_from_hex('2b7e151628aed2a6abf7158809cf4f3c');
  abIV  := bytes_from_hex('000102030405060708090a0b0c0d0e0f');
  abIn  := bytes_from_hex('6BC1BEE22E409F96E93D7E117393172A');
  abCorrect := bytes_from_hex('7649ABAC8119B246CEE98E9B12E9197D');
  pr_hexbytes('KY=', abKey);
  pr_hexbytes('IV=', abIV);
  pr_hexbytes('PT=', abIn);
  // Dimension the output buffer -- IMPORTANT
  SetLength(abOut, Length(abIn));
  ret := AES128_BytesMode(Pointer(abOut), Pointer(abIn), Length(abIn), Pointer(abKey), ENCRYPT, 'CBC', Pointer(abIV));
  Assert(0 = ret);
  pr_hexbytes('CT=', abOut);
  pr_hexbytes('OK=', abCorrect);
  // Now decrypt back to plain text
  ret := AES128_BytesMode(Pointer(abOut), Pointer(abOut), Length(abOut), Pointer(abKey), DECRYPT, 'CBC', Pointer(abIV));
  pr_hexbytes('P''=', abOut);
  Assert(0 = ret);

  WriteLn(CRLF + 'TEST TRIPLE DES WITH CONTEXT:');
  pthex := '4e6f772069732074';
  keyhex := '0123456789abcdef23456789abcdef01456789abcdef0123';
  ivhex := '1234567890abcdef';
  correcthex := 'cb191f85d1ed8439';

  WriteLn('TDEA Monte Carlo TCBC Mode Encrypt:');
  WriteLn('KY=' + keyhex);
  WriteLn('IV=' + ivhex);
  WriteLn('PT=' + pthex);
  
  hContext := TDEA_InitHex(keyhex, ENCRYPT, 'CBC', ivhex);
  WriteLn('hContext=0x' + IntToHex(hContext, 8) + ' (expected nonzero)');
  If hContext = 0 Then
      halt;
  // Do 10,000 times, working with byte arrays
  niter := 10000;
  // Set initial data by reading in from hex strings
  nextblk := bytes_from_hex(pthex);
  lastblk := bytes_from_hex(ivhex);
  For i := 0 To niter-1 do
  begin
    block := bytes_copy(nextblk);
      TDEA_Update(hContext, Pointer(block), Length(block));
      nextblk := bytes_copy(lastblk);
      lastblk := bytes_copy(block);
  end;
  WriteLn('After ' + IntToStr(niter) + ' iterations,');
  WriteLn('CT=' + bytes_to_hex(block));
  WriteLn('OK=' + correcthex);
  TDEA_Final(hContext);
  isok := (0 = AnsiCompareText(bytes_to_hex(block), string(correcthex)));
  if isok then
    WriteLn('OK, result is correct.')
  else
    WriteLn('ERROR: result is wrong!');
  assert(isok);

  WriteLn(CRLF + 'TEST HASH FUNCTIONS:');
  // Pre-dimension with the largest buffer we might need for a hash (then we'll Trim it later)
  digesthex := AnsiString(StringOfChar(#0, API_MAX_HASH_CHARS));
  s := AnsiString('abc');
  // Note how we can pass a pointer to a String even though the function expects a PByte
  // (well, it works for us...)
  ret := HASH_HexFromBytes(Pointer(digesthex), Length(digesthex), Pointer(s), Length(s), API_HASH_SHA1);
  WriteLn('SHA-1('''+ string(s) + ''')="' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);
  Assert(AnsiCompareText(string(digesthex),OK_SHA1_ABC)=0);

  // Use a hex value instead (0x616263 <=> 'abc')
  strhex := AnsiString('616263');
  digesthex := AnsiString(StringOfChar(#0, API_MAX_HASH_CHARS));
  ret := HASH_HexFromHex(Pointer(digesthex), Length(digesthex), strhex, API_HASH_SHA1);
  WriteLn('SHA-1(0x'+ string(strhex) + ')="' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);

  // Create a new text file containing only the 3-letter string 'abc'
  fname := 'abc.txt';
  create_text_file(string(fname), 'abc');
  digesthex := AnsiString(StringOfChar(#0, API_MAX_HASH_CHARS));
  ret := HASH_HexFromFile(Pointer(digesthex), Length(digesthex), fname, API_HASH_SHA1);
  WriteLn('SHA-1('+ string(fname) + ')="' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);

  // Use a different hash algorithm on the same text file
  digesthex := AnsiString(StringOfChar(#0, API_MAX_HASH_CHARS));
  ret := HASH_HexFromFile(Pointer(digesthex), Length(digesthex), fname, API_HASH_SHA512);
  WriteLn('SHA-512('+ string(fname) + ')=' + CRLF + '"' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);
  Assert(AnsiCompareText(string(digesthex),OK_SHA512_ABC)=0);

  // Compute message digest of 'abc' for other hash functions
  s := AnsiString('abc');
  digesthex := AnsiString(StringOfChar(#0, API_MAX_HASH_CHARS));

  ret := HASH_HexFromBytes(Pointer(digesthex), Length(digesthex), Pointer(s), Length(s), API_HASH_MD5);
  WriteLn('MD5('''+ string(s) + ''')="' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);
  Assert(AnsiCompareText(string(digesthex),OK_MD5_ABC)=0);

  ret := HASH_HexFromBytes(Pointer(digesthex), Length(digesthex), Pointer(s), Length(s), API_HASH_RMD160);
  WriteLn('RMD160('''+ string(s) + ''')=' + CRLF +'"' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);
  Assert(AnsiCompareText(string(digesthex),OK_RMD160_ABC)=0);

  ret := HASH_HexFromBytes(Pointer(digesthex), Length(digesthex), Pointer(s), Length(s), API_HASH_SHA224);
  WriteLn('SHA-224('''+ string(s) + ''')="' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);
  Assert(AnsiCompareText(string(digesthex),OK_SHA224_ABC)=0);

  ret := HASH_HexFromBytes(Pointer(digesthex), Length(digesthex), Pointer(s), Length(s), API_HASH_SHA256);
  WriteLn('SHA-256('''+ string(s) + ''')=' + CRLF +'"' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);
  Assert(AnsiCompareText(string(digesthex),OK_SHA256_ABC)=0);

  ret := HASH_HexFromBytes(Pointer(digesthex), Length(digesthex), Pointer(s), Length(s), API_HASH_SHA384);
  WriteLn('SHA-384('''+ string(s) + ''')=' + CRLF +'"' + Trim(string(digesthex)) + '"');
  Assert(ret > 0);
  Assert(AnsiCompareText(string(digesthex),OK_SHA384_ABC)=0);

  WriteLn(CRLF + 'TEST REPEATED USE OF SHA-1 WITH CONTEXT:');
  // Show we can add both a string and a byte array to the same context
  hContext := SHA1_Init;
  s := AnsiString('ab');
  ret := SHA1_AddString(hContext, s);
  Assert(0 = ret);
  SetLength(arrbytes, 1);
  arrbytes[0] := $63;  // 'c'
  ret := SHA1_AddBytes(hContext, Pointer(arrbytes), Length(arrbytes));
  Assert(0 = ret);
  // Finalize and form the digest
  digesthex := AnsiString(StringOfChar(#0, API_MAX_HASH_CHARS));
  ret := SHA1_HexDigest(Pointer(digesthex), hContext);
  WriteLn('SHA-1(''ab''||' + '0x63)="' + Trim(string(digesthex)) + '"');
  Assert(0 = ret);

  // SHA-1 of one million x 'a' in blocks of 1000
  correcthex := '34AA973CD4C4DAA4F61EEB2BDBAD27316534016F';
  hContext := SHA1_Init;
  s := AnsiString(StringOfChar('a', 1000));
  for i := 1 to 1000 do
    SHA1_AddString(hContext, s);
  digesthex := AnsiString(StringOfChar(#0, API_MAX_HASH_CHARS));
  ret := SHA1_HexDigest(Pointer(digesthex), hContext);
  WriteLn('SHA-1(1 million x "a")="' + Trim(string(digesthex)) + '"');
  WriteLn('Correct=               "' + correcthex + '"');
  Assert(0 = ret);
  Assert(AnsiCompareText(string(digesthex), string(correcthex)) = 0);

  WriteLn(CRLF + 'TEST GCM AUTHENTICATED ENCRYPTION:');
  // This must be most complicated function in the CryptoSys API library.
  // It is the only one to output to two variables.
  // We add to the fun by using a static byte array for the output tag ,
  // dynamic arrays for others,
  // and constant byte arrays for some of the data.

  WriteLn('Test case 6:');
  pr_hexbytes('K =', gcm_key);
  pr_hexbytes('PT=', gcm_pt);
  pr_hexbytes('A =', gcm_adata);
  pr_hexbytes('IV=', gcm_iv);
  SetLength(abOut, Length(gcm_pt));
  // CAUTION with difference between pointers to dynamic and static arrays!!
  ret := GCM_Encrypt(Pointer(abOut), Length(abOut),
    @tag[0], Length(tag),
    @gcm_pt[0], Length(gcm_pt),
    @gcm_key[0], Length(gcm_key),
    @gcm_iv[0], Length(gcm_iv),
    @gcm_adata[0], Length(gcm_adata),
    0);
  WriteLn('GCM_Encrypt returns ' + IntToStr(ret) + ' (expected 0)');
  pr_hexbytes('CT=', abOut);
  pr_hexbytes('T =', tag);
  // Compare results with correct values
  isok := CompareMem(abOut, @gcm_ok_ct, Length(gcm_ok_ct));
  WriteLn('CompareMem(ciphertext, correct) returns ' + BoolToStr(isok, True));
  isok := CompareMem(@tag, @gcm_ok_tag, Length(gcm_ok_tag));
  WriteLn('CompareMem(tag, correct) returns ' + BoolToStr(isok, True));
  // Now decrypt
  abIn := abOut;
  // COMMENT: for some reason we have to re-do SetLength here or it fails...
  SetLength(abOut, Length(abIn));
   ret := GCM_Decrypt(Pointer(abOut), Length(abOut),
    Pointer(abIn), Length(abIn),
    @gcm_key[0], Length(gcm_key),
    @gcm_iv[0], Length(gcm_iv),
    @gcm_adata[0], Length(gcm_adata),
    @tag[0], Length(tag),
    0);
  WriteLn('GCM_Decrypt returns ' + IntToStr(ret) + ' (expected 0)');
  pr_hexbytes('P''=', abOut);
  isok := CompareMem(abOut, @gcm_pt, Length(gcm_pt));
  WriteLn('CompareMem(plaintext, correct) returns ' + BoolToStr(isok, True));

  WriteLn(CRLF + 'TEST GMAC AUTHENTICATION:');
  // Ref: http://www.mail-archive.com/stds-p1619@listserv.ieee.org/msg00321.html
  abKey := bytes_from_hex('feffe9928665731c6d6a8f9467308308');
  abIV  := bytes_from_hex('cafebabefacedbaddecaf888');
  abAAD := bytes_from_hex('feedfacedeadbeeffeedfacedeadbeef');
  abCorrect := bytes_from_hex('54df474f4e71a9ef8a09bf30da7b1a92');
  pr_hexbytes('KEY=', abKey);
  pr_hexbytes('IV =', abIV);
  pr_hexbytes('AAD=', abAAD);
  // GMAC := Encrypt with no CT output or PT input. GMAC value is in tag.
  ret := GCM_Encrypt(NIL, 0,
    @tag[0], Length(tag),
    NIL, 0,
    Pointer(abKey), Length(abKey),
    Pointer(abIV), Length(abIV),
    Pointer(abAAD), Length(abAAD),
    0);
  WriteLn('GCM_Encrypt(GMAC) returns ' + IntToStr(ret) + ' (expected 0)');
  pr_hexbytes('TAG=', tag);
  pr_hexbytes('OK =', abCorrect);
  isok := CompareMem(@tag, abCorrect, Length(abCorrect));
  WriteLn('CompareMem(gmac, correct) returns ' + BoolToStr(isok, True));

  WriteLn(CRLF + 'TEST STREAM CIPHER:');
  WriteLn('Salsa20:');
  keyhex := '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F';
  ivhex := '0000000000000000';
  // Ref: eSTREAM `verified.test-vectors.txt` Set 3, vector# 0:
  inputhex   := '0000000000000000000000000000000000000000000000000000000000000000';
  correcthex := 'B580F7671C76E5F7441AF87C146D6B513910DC8B4146EF1B3211CF12AF4A4B49';
  // Set sOutput to be same length as sInput--IMPORTANT
  outputhex := AnsiString(StringOfChar(#0,Length(inputhex)));
  WriteLn('KY=' + keyhex);
  WriteLn('IV=' + ivhex);
  WriteLn('PT=' + inputhex);
  // Encipher using Salsa20 in hex mode
  ret := CIPHER_StreamHex(Pointer(outputhex), Length(outputhex), inputhex, keyhex, ivhex, 0, API_SC_SALSA20);
  WriteLn('CT=' + string(outputhex) + ' ret=' + IntToStr(ret));
  WriteLn('OK=' + correcthex);
  // Now decrypt back to plain text (just encrypt again)
  inputhex := outputhex;
  ret := CIPHER_StreamHex(Pointer(outputhex), Length(outputhex), inputhex, keyhex, ivhex, 0, API_SC_SALSA20);
  WriteLn('P''=' + string(outputhex) + ' ret=' + IntToStr(ret));
  Assert(AnsiCompareStr(string(outputhex), string(inputhex))= 0);

  WriteLn('ChaCha20:');
  // ChaCha20 test vector from "ChaCha20 and Poly1305 for IETF protocols
  abKey := bytes_from_hex('000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F');
  abIV  := bytes_from_hex('000000000000004a00000000');
  // ''
  abIn  := bytes_from_string('Ladies and Gentlemen of the class of ''99:');
  abCorrect := bytes_from_hex('6E2E359A2568F98041BA0728DD0D6981E97E7AEC1D4360C20A27AFCCFD9FAE0BF91B65C5524733AB8F');
  pr_hexbytes('KY=', abKey);
  pr_hexbytes('IV=', abIV);
  pr_hexbytes('PT=', abIn);
  // Dimension the output buffer -- IMPORTANT
  SetLength(abOut, Length(abIn));
  ret := CIPHER_StreamBytes(Pointer(abOut), Pointer(abIn), Length(abIn), Pointer(abKey), Length(abKey), Pointer(abIV), Length(abIV), 1, API_SC_CHACHA20);
  Assert(0 = ret);
  pr_hexbytes('CT=', abOut);
  pr_hexbytes('OK=', abCorrect);
  isok := CompareMem(abOut, abCorrect, Length(abCorrect));
  Assert(isok);
  // Now decrypt back to plain text
  ret := CIPHER_StreamBytes(Pointer(abOut), Pointer(abOut), Length(abOut), Pointer(abKey), Length(abKey), Pointer(abIV), Length(abIV), 1, API_SC_CHACHA20);
  pr_hexbytes('P''=', abOut);
  WriteLn('"' + bytes_to_string(abOut) + '"'); 
  Assert(0 = ret);
  isok := CompareMem(abOut, abIn, Length(abCorrect));
  Assert(isok);

  WriteLn(CRLF+'ALL DONE.');

end;

// MAIN PROCEDURE
begin
  try
    do_tests;

   except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
   end;
 end.