/// certificate-based public-key cryptography using ECC-secp256r1
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynEcc;

(*
    This file is part of Synopse framework.

    Synopse framework. Copyright (C) 2022 Arnaud Bouchez
      Synopse Informatique - https://synopse.info

  *** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.

  The Original Code is Synopse framework.

  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2022
  the Initial Developer. All Rights Reserved.

  Contributor(s):
   - Kenneth MacKay (micro-ecc source code)

  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the GPL or the LGPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****


  Using secp256r1 curve from "simple and secure ECDH and ECDSA library"
  Copyright (c) 2013, Kenneth MacKay - BSD 2-clause license
  https://github.com/kmackay/micro-ecc

  *** BEGIN LICENSE BLOCK *****
  Copyright (c) 2013, Kenneth MacKay
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:
   * Redistributions of source code must retain the above copyright notice, this
     list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  ***** END LICENSE BLOCK *****


  TODO:
  - secure sign-then-crypt by signing the destination name with the plain content
    to avoid "Surreptitious Forwarding" (reuse of the plain content to another
    recipier) - see http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html

*)

{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER

interface

uses
  {$ifdef MSWINDOWS}
    Windows, // for CriticalSection API inling
  {$else}  // for GetFileSize emulated API
    {$ifdef KYLIX3}
      SynKylix,
    {$endif}
    {$ifdef FPC}
      SynFPCLinux,
    {$endif}
  {$endif MSWINDOWS}
  SysUtils,
  Classes,
  Contnrs,
  SynCommons,
  SynTable,
  SynCrypto;


{ *********** low-level ECC secp256r1 ECDSA and ECDH functions *********** }

{$ifdef CPUINTEL}

  {$ifdef CPUX86}
    {$ifdef KYLIX3}
      {$define ECC_STATICLIB_AVAILABLE}
      {$define ECC_32ASM}     // gcc -g -O1 -c ecc.c
    {$else}
      {$ifndef BSD}
        {$define ECC_STATICLIB_AVAILABLE}
        {.$define ECC_32ASM}    // gcc -g -O1 -c ecc.c
        {.$define ECC_O1}       // gcc -g -O1 -c ecc.c
        {$define ECC_O2}        // gcc -g -O2 -c ecc.c
        {.$define ECC_O3}       // gcc -g -O3 -c ecc.c
      {$endif}
    {$endif KYLIX}
  {$endif CPUX86}

  {$ifdef CPUX64}
    {$ifndef BSD}
      {$define ECC_STATICLIB_AVAILABLE}
      {.$define ECC_O1}       // gcc -g -O1 -c ecc.c
      {$define ECC_O2}        // gcc -g -O2 -c ecc.c
      {.$define ECC_O3}       // gcc -g -O3 -c ecc.c
    {$endif}
  {$endif CPUX64}

{$endif CPUINTEL}

const
  /// the size of the 256-bit memory structure used for secp256r1
  // - map 32 bytes of memory
  ECC_BYTES = sizeof(THash256);

type
  /// store a public key for ECC secp256r1 cryptography
  // - use ecc_make_key() to generate such a key
  // - stored in compressed form with its standard byte header, i.e. each
  // public key consumes 33 bytes of memory
  TECCPublicKey = array[0..ECC_BYTES] of byte;
  /// store a public key for ECC secp256r1 cryptography
  // - use ecc_uncompress_key_pas() to compute such a key from a TECCPublicKey
  // - stored in uncompressed form, consuming 64 bytes of memory
  TECCPublicKeyUncompressed = array[0..(ECC_BYTES*2)-1] of byte;
  /// store a private key for ECC secp256r1 cryptography
  // - use ecc_make_key() to generate such a key
  // - stored in compressed form, i.e. each private key consumes 32 bytes of memory
  TECCPrivateKey = array[0..ECC_BYTES-1] of byte;

  /// store a 256-bit hash, as expected by ECC secp256r1 cryptography
  // - see e.g. ecdsa_sign() and ecdsa_verify() functions
  TECCHash = THash256;
  /// store a signature, as generated by ECC secp256r1 cryptography
  // - see e.g. ecdsa_sign() and ecdsa_verify() functions
  // - contains ECDSA's R and S integers
  // - each ECC signature consumes 64 bytes of memory
  TECCSignature = array[0..(ECC_BYTES*2)-1] of byte;

  /// store a signature, in the DER format
  // - static allocated buffer as returned by EccSignToDer()
  TEccSignatureDer = array[0..(ECC_BYTES * 2) + 7] of byte;

  /// store an encryption key, as generated by ECC secp256r1 cryptography
  // - use ecdh_shared_secret() to compute such a key from public/private keys
  // - 256-bit / 32 bytes derivation from secp256r1 ECDH is expected to have at
  // least 247 bits of entropy so could better be derivated via a KDF before used
  // as encryption secret - see @http://crypto.stackexchange.com/a/9428/40200
  TECCSecretKey = THash256;

  PECCPublicKey = ^TECCPublicKey;
  PECCPrivateKey = ^TECCPrivateKey;
  PECCHash = ^TECCHash;
  PECCSignature = ^TECCSignature;
  PECCSecretKey = ^TECCSecretKey;

{$ifdef ECC_32ASM}
var
  /// create a public/private key pair for further ECC cryptographic process
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - returns true if the key pair was generated successfully in pub/priv
  // - returns false if an error occurred
  ecc_make_key: function(out pub: TECCPublicKey; out priv: TECCPrivateKey): boolean; cdecl;

  /// compute an ECDH shared secret given your secret key and someone else's public key
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - note: it is recommended that you hash the result of ecdh_shared_secret
  // before using it for symmetric encryption or HMAC (via an intermediate KDF)
  // - returns true if the shared secret was generated successfully in secret
  // - returns false if an error occurred
  ecdh_shared_secret: function(const pub: TECCPublicKey; const priv: TECCPrivateKey;
    out secret: TECCSecretKey): boolean; cdecl;

  /// generate an ECDSA signature for a given hash value
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - returns true if the signature generated successfully in sign
  // - returns false if an error occurred
  ecdsa_sign: function(const priv: TECCPrivateKey; const hash: TECCHash;
    out sign: TECCSignature): boolean; cdecl;

  /// verify an ECDSA signature
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - returns true if the signature is valid
  // - returns false if it is invalid
  ecdsa_verify: function(const pub: TECCPublicKey; const hash: TECCHash;
    const sign: TECCSignature): boolean; cdecl;

{$else}

/// create a public/private key pair
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked micro-ecc library function
// - returns true if the key pair was generated successfully in pub/priv
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecc_make_key(out pub: TECCPublicKey; out priv: TECCPrivateKey): boolean;
  {$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}

/// compute a shared secret given your secret key and someone else's public key
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked micro-ecc library function
// - note: it is recommended that you hash the result of ecdh_shared_secret
// before using it for symmetric encryption or HMAC (via an intermediate KDF)
// - returns true if the shared secret was generated successfully in secret
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecdh_shared_secret(const pub: TECCPublicKey; const priv: TECCPrivateKey;
  out secret: TECCSecretKey): boolean;
  {$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}

/// generate an ECDSA signature for a given hash value
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked micro-ecc library function
// - returns true if the signature generated successfully in sign
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecdsa_sign(const priv: TECCPrivateKey; const hash: TECCHash;
  out sign: TECCSignature): boolean;
  {$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}

/// verify an ECDSA signature
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked micro-ecc library function
// - returns true if the signature is valid
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecdsa_verify(const pub: TECCPublicKey; const hash: TECCHash;
  const sign: TECCSignature): boolean;
  {$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}

{$endif ECC_32ASM}


/// pascal function to create a secp256r1 public/private key pair
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecc_make_key_pas(out PublicKey: TECCPublicKey; out PrivateKey: TECCPrivateKey): boolean;

/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in compressed format)
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecdh_shared_secret_pas(const PublicKey: TECCPublicKey;
  const PrivateKey: TECCPrivateKey; out Secret: TECCSecretKey): boolean; overload;

/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TECCPublicKey,
// since public key doesn't need to be uncompressed
function ecdh_shared_secret_pas(const PublicPoint: TECCPublicKeyUncompressed;
  const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean; overload;

/// pascal function to generate an ECDSA secp256r1 signature for a given hash value
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecdsa_sign_pas(const PrivateKey: TECCPrivateKey; const Hash: TECCHash;
  out Signature: TECCSignature): boolean;

/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in compressed format)
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecdsa_verify_pas(const PublicKey: TECCPublicKey; const Hash: TECCHash;
  const Signature: TECCSignature): boolean; overload;

/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TECCPublicKey,
// since public key doesn't need to be uncompressed
function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed;
  const Hash: TECCHash; const Signature: TECCSignature): boolean; overload;

/// uncompress a public key for ECC secp256r1 cryptography
// - convert from its compressed form with its standard byte header
// (33 bytes of memory) into uncompressed/flat form (64 bytes of memory)
procedure ecc_uncompress_key_pas(const Compressed: TECCPublicKey;
  out Uncompressed: TECCPublicKeyUncompressed);



{ *********** middle-level certificate-based public-key cryptography *********** }

type
  /// used to identify a TECCCertificate
  // - could be generated by TAESPRNG.Fill() method
  TECCCertificateID = type THash128;

  /// used to identify a TECCCertificate issuer
  // - could be generated by AsciiToBaudot(), with truncation to 16 bytes
  // (up to 25 Ascii-7 characters)
  TECCCertificateIssuer = type THash128;

  /// used to store a date in a TECCCertificate
  // - i.e. 16-bit number of days since 1 August 2016
  // - use NowECCDate, ECCDate(), ECCToDateTime() or ECCText() functions
  TECCDate = word;

  PECCCertificateID = ^TECCCertificateID;
  PECCCertificateIssuer = ^TECCCertificateIssuer;
  PECCDate = ^TECCDate;

  /// the certification information of a TECCCertificate
  // - as stored in TECCCertificateContent.Signed
  // - defined in a separate record, to be digitaly signed in the Signature field
  // - map TECCCertificate.Version 1 of the binary format
  // - "self-signed" certificates may be used as "root" certificates in the
  // TECCCertificateChain list
  TECCCertificateSigned = packed record
    /// when this certificate was generated
    IssueDate: TECCDate;
    /// certificate valid not before
    ValidityStart: TECCDate;
    /// certificate valid not after
    ValidityEnd: TECCDate;
    /// a genuine identifier for this certificate
    // - is used later on to validate other certificates in chain
    Serial: TECCCertificateID;
    /// identify the certificate issuer
    // - is either geniune random bytes, or some Baudot-encoded text
    Issuer: TECCCertificateIssuer;
    /// genuine identifier of the authority certificate used for signing
    // - should be used to retrieve the associated PublicKey used to compute
    // the Signature field
    // - may equal Serial, if was self-signed
    AuthoritySerial: TECCCertificateID;
    /// identify the authoritify issuer used for signing
    // - is either geniune random bytes, or some Baudot-encoded text
    // - may equal Issuer, if was self-signed
    AuthorityIssuer: TECCCertificateIssuer;
    /// the ECDSA secp256r1 public key of this certificate
    // - may be used later on for signing or key derivation
    PublicKey: TECCPublicKey;
  end;
  /// points to certification information of a TECCCertificate
  PECCCertificateSigned = ^TECCCertificateSigned;

  /// store a TECCCertificate binary buffer for ECC secp256r1 cryptography
  // - i.e. a certificate public key, with its ECDSA signature
  // - would be stored in 173 bytes
  TECCCertificateContent = packed record
    /// the TECCCertificate format version
    Version: word;
    /// the certification information, digitaly signed in the Signature field
    Signed: TECCCertificateSigned;
    /// SHA-256 + ECDSA secp256r1 signature of the Certificate record
    Signature: TECCSignature;
    /// FNV-1a checksum of all previous fields
    // - we use fnv32 and not crc32c here to avoid colision with crc64c hashing
    // - avoiding to compute slow ECDSA verification in case of corruption,
    // due e.g. to unexpected transmission/bug/fuzzing
    // - should be the very last field in the record
    CRC: cardinal;
  end;
  /// points to a TECCCertificate binary buffer for ECC secp256r1 cryptography
  PECCCertificateContent = ^TECCCertificateContent;

  /// store a TECCSignatureCertified binary buffer for ECDSA secp256r1 signature
  // - i.e. the digital signature of some content
  TECCSignatureCertifiedContent = packed record
    /// the TECCSignatureCertificated format version
    Version: word;
    /// when this signature was generated
    Date: TECCDate;
    /// genuine identifier of the authority certificate used for signing
    // - should be used to retrieve the associated PublicKey used to compute
    // the Signature field
    AuthoritySerial: TECCCertificateID;
    /// identify the authoritify issuer used for signing
    // - is either geniune random bytes, or some Baudot-encoded text
    AuthorityIssuer: TECCCertificateIssuer;
    /// SHA-256 + ECDSA secp256r1 digital signature of the content
    Signature: TECCSignature;
  end;
  /// points to a TECCSignatureCertified buffer for ECDSA secp256r1 signature
  PECCSignatureCertifiedContent = ^TECCSignatureCertifiedContent;

  /// the known algorithms implemented in ECIES encryption
  // - supports AES 256-bit encryption with safe block modes (weack ECB mode
  // is not available) - or AES 128-bit if needed (e.g. for regulatory issues)
  // - safe HMAC SHA-256 is used as Message Authentication Code algorithm
  // - optional SynLZ compression can be enabled
  TECIESAlgo = (
    ecaUnknown,
    ecaPBKDF2_HMAC_SHA256_AES256_CFB,
    ecaPBKDF2_HMAC_SHA256_AES256_CBC,
    ecaPBKDF2_HMAC_SHA256_AES256_OFB,
    ecaPBKDF2_HMAC_SHA256_AES256_CTR,
    ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES256_CBC_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES256_OFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES256_CTR_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CBC_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_OFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CTR_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CFB,
    ecaPBKDF2_HMAC_SHA256_AES128_CBC,
    ecaPBKDF2_HMAC_SHA256_AES128_OFB,
    ecaPBKDF2_HMAC_SHA256_AES128_CTR);

  /// binary header of a .synecc file, encrypted via ECC secp256r1
  // - as generated by TECCCertificate.Encrypt/EncryptFile, and decoded by
  // TECCCertificateSecret.Decrypt
  // - a sign-then-encrypt pattern may have been implemented for additional safety
  TECIESHeader = packed record
    /// contains 'SynEccEncrypted'#26
    // - so every .synecc file starts with those characters as signature
    magic: THash128;
    /// TECCCertificate.Issuer of the recipient public key used for encryption
    // - is either geniune random bytes, or some Baudot-encoded text
    rec: TECCCertificateIssuer;
    /// TECCCertificate.Serial of the recipient public key used for encryption
    recid: TECCCertificateID;
    /// the size of the plain content (may be compressed before encryption)
    size: cardinal;
    /// when this encryption was performed
    date: TECCDate;
    /// optional timestamp, in Unix seconds since 1970, of the source file
    unixts: cardinal;
    /// actual encryption algorithm used
    algo: TECIESAlgo;
    /// the genuine random public key used for encryption
    rndpub: TECCPublicKey;
    /// optional ECDSA secp256r1 digital signature of the plain content
    sign: TECCSignatureCertifiedContent;
    /// the Message Authentication Code of the encrypted content
    hmac: THash256;
    /// a crc32c hash of the header (excluding this field)
    crc: cardinal;
  end;
  /// points to the binary header of a .synecc encrypted file
  PECIESHeader = ^TECIESHeader;

  /// indicate the validity state of a ECDSA signature against a certificate
  // - as returned by low-level ECCVerify() function, and
  // TECCSignatureCertified.Verify, TECCCertificateChain.IsValid or
  // TECCCertificateChain.IsSigned methods
  // - see also ECC_VALIDSIGN constant
  TECCValidity = (
    ecvUnknown,
    ecvValidSigned, ecvValidSelfSigned,
    ecvNotSupported, ecvBadParameter, ecvCorrupted,
    ecvInvalidDate, ecvUnknownAuthority, ecvDeprecatedAuthority,
    ecvInvalidSignature);

  /// the error codes returned by TECCCertificateSecret.Decrypt()
  // - see also ECC_VALIDDECRYPT constant
  TECCDecrypt = (
    ecdDecrypted, ecdDecryptedWithSignature,
    ecdNoContent, ecdCorrupted, ecdInvalidSerial, ecdNoPrivateKey,
    ecdInvalidMAC, ecdDecryptError, ecdWriteFileError);

type
  /// the Authentication schemes recognized by TECDHEProtocol
  // - specifying the authentication allows a safe one-way handshake
  TECDHEAuth = (authMutual, authServer, authClient);
  /// set of Authentication schemes recognized by TECDHEProtocolServer
  TECDHEAuths = set of TECDHEAuth;
  /// the Key Derivation Functions recognized by TECDHEProtocol
  // - used to compute the EF secret and MAC secret from shared ephemeral secret
  // - only HMAC SHA-256 safe algorithm is proposed currently
  TECDHEKDF = (kdfHmacSha256);
  /// the Encryption Functions recognized by TECDHEProtocol
  // - all supported AES chaining blocks have their 128-bit and 256-bit flavours
  // - default efAesCrc128 will use the dedicated TAESCFBCRC class, i.e.
  // AES-CFB encryption with on-the-fly 256-bit CRC computation of the plain and
  // encrypted blocks, and AES-encryption of the CRC to ensure cryptographic
  // level message authentication and integrity - associated TECDHEMAC
  // property should be macDuringEF
  // - other values will define TAESCFB/TAESOFB/TAESCTR/TAESCBC in 128-bit or
  // 256-bit mode, in conjunction with a TECDHEMAC setting
  // - AES-NI hardware acceleration will be used, if available - under x86-64,
  // efAesOfb128 will potentially give the best performance
  // - of course, weack ECB mode is not available
  TECDHEEF = (efAesCrc128, efAesCfb128, efAesOfb128, efAesCtr128, efAesCbc128,
              efAesCrc256, efAesCfb256, efAesOfb256, efAesCtr256, efAesCbc256);
  /// the Message Authentication Codes recognized by TECDHEProtocol
  // - default macDuringEF (680MB/s for efAesCrc128 with SSE4.2 and AES-NI)
  // means that no separated MAC is performed, but done during encryption step:
  // only supported by efAesCrc128 or efAesCrc256 (may be a future AES-GCM)
  // - macHmacSha256 is the safest, but slow, especially when used as MAC for
  // AES-NI accellerated encryption (110MB/s with efAesCfb128, to be compared
  // with macDuringEF, which produces a similar level of MAC)
  // - macHmacCrc256c and macHmacCrc32c are faster (550-650MB/s with efAesCfb128),
  // and prevent transmission errors but not message integrity or authentication
  // since composition of two crcs is a multiplication by a polynomial - see
  // http://mslc.ctf.su/wp/boston-key-party-ctf-2016-hmac-crc-crypto-5pts
  // - macXxHash32 will use the xxhash32() algorithm, fastest without SSE4.2
  // - macNone (800MB/s, which is the speed of AES-NI encryption itself for a
  // random set of small messages) won't check errors, but only replay attacks
  TECDHEMAC = (macDuringEF, macHmacSha256, macHmacCrc256c, macHmacCrc32c,
    macXxHash32, macNone);

  /// defines one protocol Algorithm recognized by TECDHEProtocol
  // - only safe and strong parameters are allowed, and the default values
  // (i.e. all fields set to 0) will ensure a very good combination
  // - in current implementation, there is no negociation between nodes:
  // client and server should have the very same algorithm
  TECDHEAlgo = packed record
    /// the current Authentication scheme
    auth: TECDHEAuth;
    /// the current Key Derivation Function
    kdf: TECDHEKDF;
    /// the current Encryption Function
    ef: TECDHEEF;
    /// the current Message Authentication Code
    mac: TECDHEMAC;
  end;
  /// points to one protocol Algorithm recognized by TECDHEProtocol
  PECDHEAlgo = ^TECDHEAlgo;

  /// the binary handshake message, sent by client to server
  // - the frame will always have the same fixed size of 290 bytes (i.e. 388
  // base64-encoded chars, which could be transmitted in a HTTP header),
  // for both mutual or unilateral authentication
  // - ephemeral keys may be included for perfect forward security
  TECDHEFrameClient = packed record
    /// expected algorithm used
    algo: TECDHEAlgo;
    /// a client-generated random seed
    RndA: THash128;
    /// client public key, with its certificate
    // - may be zero, in case of unilateral authentication (algo=authServer)
    QCA: TECCCertificateContent;
    /// client-generated ephemeral public key
    // - may be zero, in case of unilateral authentication (algo=authClient)
    QE: TECCPublicKey;
    /// SHA-256 + ECDSA secp256r1 signature of the previous fields, computed
    // with the client private key
    // - i.e. ECDSASign(dA,sha256(algo|RndA|QCA|QE))
    // - may be zero, in case of unilateral authentication (algo=authServer)
    Sign: TECCSignature;
  end;

  /// the binary handshake message, sent back from server to client
  // - the frame will always have the same fixed size of 306 bytes (i.e. 408
  // base64-encoded chars, which could be transmitted in a HTTP header),
  // for both mutual or unilateral authentication
  // - ephemeral keys may be included for perfect forward security
  TECDHEFrameServer = packed record
    /// algorithm used by the server
    algo: TECDHEAlgo;
    /// client-generated random seed
    RndA: THash128;
    /// a server-generated random seed
    RndB: THash128;
    /// server public key, with its certificate
    // - may be zero, in case of unilateral authentication (algo=authClient)
    QCB: TECCCertificateContent;
    /// server-generated ephemeral public key
    // - may be zero, in case of unilateral authentication (algo=authServer)
    QF: TECCPublicKey;
    /// SHA-256 + ECDSA secp256r1 signature of the previous fields, computed
    // with the server private key
    // - i.e. ECDSASign(dB,sha256(algo|RndA|RndB|QCB|QF))
    // - may be zero, in case of unilateral authentication (algo=authClient)
    Sign: TECCSignature;
  end;

const
  /// TECCValidity results indicating a valid digital signature
  ECC_VALIDSIGN = [ecvValidSigned, ecvValidSelfSigned];

  /// TECCDecrypt results indicating a valid decryption process
  ECC_VALIDDECRYPT = [ecdDecrypted, ecdDecryptedWithSignature];

/// fill all bytes of this ECC private key buffer with zero
// - may be used to cleanup stack-allocated content
// ! ... finally FillZero(PrivateKey); end;
procedure FillZero(out Priv: TECCPrivateKey); overload;

/// returns the current UTC date, as a TECCDate integer value
// - i.e. 16-bit number of days since 1 August 2016
function NowECCDate: TECCDate;
  {$ifdef HASINLINE}inline;{$endif}

/// convert a supplied TDateTime value into a TECCDate integer value
// - i.e. 16-bit number of days since 1 August 2016
// - returns 0 if the supplied value is invalid, i.e. out of range
function ECCDate(const DateTime: TDateTime): TECCDate;

/// convert a supplied a TECCDate integer value into a TDateTime value
// - i.e. 16-bit number of days since 1 August 2016
function ECCToDateTime(ECCDate: TECCDate): TDateTime;
  {$ifdef HASINLINE}inline;{$endif}

/// convert a supplied a TECCDate integer value into a ISO-8601 text value
// - i.e. 16-bit number of days since 1 August 2016
function ECCText(ECCDate: TECCDate; Expanded: boolean=true): RawUTF8; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// compare two TECCCertificateIssuer binary buffer values
function IsEqual(const issuer1,issuer2: TECCCertificateIssuer): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// compare two TECCCertificateID binary buffer values
function IsEqual(const id1,id2: TECCCertificateID): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// ensure a TECCCertificateIssuer binary buffer is not void, i.e. filled with 0
function IsZero(const issuer: TECCCertificateIssuer): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// ensure a TECCCertificateID binary buffer is not void, i.e. filled with 0
function IsZero(const id: TECCCertificateID): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// convert a supplied TECCCertificateIssuer binary buffer into proper text
// - returns Ascii-7 text if was stored using Baudot encoding
// - or returns hexadecimal values, if it was 16 bytes of random binary
function ECCText(const Issuer: TECCCertificateIssuer): RawUTF8; overload;

/// convert some Ascii-7 text into a TECCCertificateIssuer binary buffer
// - using Emile Baudot encoding
// - returns TRUE on Text truncation to fit into the 16 bytes
function ECCIssuer(const Text: RawUTF8; out Issuer: TECCCertificateIssuer): boolean;

/// convert a supplied TECCCertificateID binary buffer into proper text
// - returns hexadecimal values, or '' if the ID is filled with zeros
function ECCText(const ID: TECCCertificateID): RawUTF8; overload;

/// convert a supplied hexadecimal buffer into a TECCCertificateID binary buffer
// - returns TRUE if the supplied Text was a valid hexadecimal buffer
function ECCID(const Text: RawUTF8; out ID: TECCCertificateID): boolean;

/// fast check of the binary buffer storage of a certificate
// - ensure content.CRC has the expected value, using FNV-1a checksum
// - does not validate the certificate against the certificates chain, nor
// perform any ECC signature: use TECCCertificateChain.IsValid instead
function ECCCheck(const content: TECCCertificateContent): boolean; overload;

/// fast check of the dates stored in a certificate binary buffer
// - could be validated against ECCCheck()
function ECCCheckDate(const content: TECCCertificateContent): boolean;

/// fast check if the binary buffer storage of a certificate was self-signed
// - a self-signed certificate will have its AuthoritySerial/AuthorityIssuer
// fields matching Serial/Issuer
function ECCSelfSigned(const content: TECCCertificateContent): boolean;

/// fast check of the binary buffer storage of a signature
// - just check that the date and authority are set
function ECCCheck(const content: TECCSignatureCertifiedContent): boolean; overload;

/// convert a supplied base-64 text into a TECCSignatureCertifiedContent binary buffer
function ECCSign(const base64: RawUTF8; out content: TECCSignatureCertifiedContent): boolean;

/// convert a raw signature into a DER compatible content
// - returns the number of bytes encoded into der[] buffer
function EccSignToDer(const sign: TEccSignature; out der: TEccSignatureDer): integer;

/// convert a supplied TECCSignatureCertifiedContent binary buffer into proper text
// - returns base-64 encoded text, or '' if the signature was filled with zeros
function ECCText(const sign: TECCSignatureCertifiedContent): RawUTF8; overload;

/// convert a supplied TECCSignature binary buffer into proper text
// - returns base-64 encoded text, or '' if the signature was filled with zeros
function ECCText(const sign: TECCSignature): RawUTF8; overload;

/// low-level verification of a TECCSignatureCertifiedContent binary buffer
// - will verify all internal signature fields according to a supplied authority,
// then will perform the ECDSA verification of the supplied 256-bit hash with
// the authority public key
// - as used by TECCSignatureCertified.Verify and TECCCertificateChain.IsValid
function ECCVerify(const sign: TECCSignatureCertifiedContent;
  const hash: THash256; const auth: TECCCertificateContent): TECCValidity;

/// validate the binary header of a .synecc file buffer, encrypted via ECC secp256r1
// - will check against the expected layout, and values stored (e.g. crc)
// - returns true if head is a valid .synecc header, false otherwise
function ECIESHeader(const head: TECIESHeader): boolean; overload;

/// extract the binary header of a .synecc file buffer, encrypted via ECC secp256r1
// - match the format generated by TECCCertificate.Encrypt/EncryptFile
// - returns true on success, false otherwise
function ECIESHeader(const encrypted: RawByteString; out head: TECIESHeader): boolean; overload;

/// extract the binary header of a .synecc file, encrypted via ECC secp256r1
// - match the format generated by TECCCertificate.Encrypt/EncryptFile
// - returns true on success, false otherwise
// - if rawencryptedfile is specified, will also create such a file with the
// raw encrypted content (i.e. excluding the encryptedfile header)
function ECIESHeaderFile(const encryptedfile: TFileName; out head: TECIESHeader;
  const rawencryptedfile: TFileName=''): boolean;

/// convert the binary header of a .synecc file buffer into a JSON object
// - returns '' if the header is not a valid .synecc file
function ECIESHeaderText(const head: TECIESHeader): RawUTF8; overload;

/// convert the header of a .synecc file into a JSON object
// - returns '' if the header is not a valid .synecc file
// - if rawencryptedfile is specified, will also create such a file with the
// raw encrypted content (i.e. excluding the encryptedfile header)
function ECIESHeaderText(const encryptedfile: TFileName;
  const rawencryptedfile: TFileName=''): RawUTF8; overload;


{ *********** high-level certificate-based public-key cryptography *********** }

const
  DEFAULT_ECCROUNDS = 60000;

type
  /// exception class associated with this SynEcc unit
  EECCException = class(ESynException);

  TECCSignatureCertified = class;

  /// a public certificate using ECC secp256r1 cryptography
  // - implements a custom binary format, with validation period, and chaining
  // - could be used for safe data signing, and authentication
  // - in fact, Base64 published property is enough to persist this instance:
  // but consider also ToBase64/FromBase64/LoadFromStream/SaveToStream methods
  TECCCertificate = class(TSynPersistent)
  protected
    fContent: TECCCertificateContent;
    fStoreOnlyPublicKey: boolean;
    function GetAuthorityIssuer: RawUTF8;
    function GetAuthoritySerial: RawUTF8;
    function GetIssueDate: RawUTF8;
    function GetIssuer: RawUTF8;
    function GetSerial: RawUTF8;
    function GetValidityEnd: RawUTF8;
    function GetValidityStart: RawUTF8;
    function GetIsSelfSigned: boolean;
    function InternalLoad(const data: RawByteString): boolean; virtual;
    function InternalSave: RawByteString; virtual;
    procedure SetBase64(const base64: RawUTF8);
  public
    /// initialize this certificate
    constructor Create; override;
    /// initialize this certificate from a supplied certificate binary
    // - will raise an EECCException if the supplied binary is incorrect
    constructor CreateFrom(const binary: TECCCertificateContent); virtual;
    /// initialize this certificate from a supplied base-64 encoded binary
    // - will raise an EECCException if the supplied base64 is incorrect
    constructor CreateFromBase64(const base64: RawUTF8); virtual;
    /// initialize this certificate from a set of potential inputs
    // - will first search from a .public file name, base-64 encoded binary,
    // or a serial number which be used to search for a local .public file
    // (as located by ECCKeyFileFind)
    // - will raise an EECCException if no supplied media is correct
    constructor CreateFromAuth(const AuthPubKey: TFileName;
      const AuthBase64, AuthSerial: RawUTF8); virtual;
    /// the certification information, digitaly signed in the Signature field
    property Signed: TECCCertificateSigned read fContent.Signed;
    /// SHA-256 + ECDSA secp256r1 signature of the Certificate record
    property Signature: TECCSignature read fContent.Signature;
    /// persist the certificate as some base-64 encoded binary
    // - will use SaveToStream serialization
    function ToBase64: RawUTF8;
    /// retrieve the certificate from some base-64 encoded binary
    // - will use LoadFromStream serialization
    // - returns true on success, false otherwise
    function FromBase64(const base64: RawUTF8): boolean;
    /// retrieve the certificate from the "Base64": JSON entry of a .public file
    // - will use FromBase64/LoadFromStream serialization
    // - returns true on success, false otherwise
    function FromFile(const filename: TFileName): boolean;
    /// retrieve the certificate from a set of potential inputs
    // - will first search from a .public file name, base-64 encoded binary,
    // or a serial number which be used to search for a local .public file in
    // the current folder or ECCKeyFileFolder (as located by ECCKeyFileFind)
    // - returns true on success, false otherwise
    function FromAuth(const AuthPubKey: TFileName;
      const AuthBase64, AuthSerial: RawUTF8): boolean;
    /// persist only the public certificate as some base-64 encoded binary
    // - will follow TECCCertificate.SaveToStream/ToBase64 serialization,
    // even when called from a TECCCertificateSecret instance
    // - could be used to safely publish the public information of a newly
    // created certificate
    function PublicToBase64: RawUTF8;
    /// persist the certificate as some binary
    // - returns true on success (i.e. this class stores a certificate),
    // false otherwise
    function SaveToStream(Stream: TStream): boolean;
    /// retrieve the certificate from some base-64 encoded binary
    // - returns true on success, false otherwise
    function LoadFromStream(Stream: TStream): boolean;
    /// fast check of the binary buffer storage of this certificate
    // - ensure Content.CRC has the expected value, using FNV-1a checksum
    // - does not validate the certificate against the certificates chain, nor
    // perform any ECC signature: use TECCCertificateChain.IsValid instead
    function CheckCRC: boolean;
    /// encrypt using the ECIES scheme, using this public certificate as key,
    // via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
    // - returns the encrypted content, in the .synecc optimized format
    // - optional salt information used for PBKDF2 or HMAC can be customized
    // - ecaUnknown algorithm will use either ecaPBKDF2_HMAC_SHA256_AES256_CFB
    // or ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ depending if the supplied
    // contain is compressible or not - but you may force another algorithm
    // - you can optionally associate an ECDSA secp256r1 digital signature,
    // and a timestamp which may be used when re-creating a decyphered file
    // - use TECCCertificateSecret.Decrypt to uncypher the resulting content
    function Encrypt(const Plain: RawByteString;
      Signature: TECCSignatureCertified=nil; FileDateTime: TDateTime=0;
      const KDFSalt: RawUTF8='salt'; KDFRounds: integer=DEFAULT_ECCROUNDS;
      const MACSalt: RawUTF8='hmac'; MACRounds: integer=100;
      Algo: TECIESAlgo=ecaUnknown): RawByteString;
    /// encrypt a file using the ECIES scheme, using this public certificate as
    // key,via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
    // - by default, will create a FileToCrypt.synecc encrypted file
    // - ecaUnknown algorithm will use either ecaPBKDF2_HMAC_SHA256_AES256_CFB
    // or ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ depending if the supplied
    // contain is compressible or not - but you may force another algorithm
    // - any available .sign ECDSA secp256r1 digital signature file will be
    // recognized and embedded to the resulting .synecc content
    // - optional salt information used for PBKDF2 can be customized, to lock
    // the encryted file with the supplied password
    function EncryptFile(const FileToCrypt: TFileName; const DestFile: TFileName='';
      const Salt: RawUTF8='salt'; SaltRounds: integer=DEFAULT_ECCROUNDS;
      Algo: TECIESAlgo=ecaUnknown; IncludeSignFile: boolean=true): boolean;
    {$ifndef NOVARIANTS}
    /// returns a TDocVariant object of all published properties of this instance
    // - excludes the Base64 property content if withBase64 is set to false
    function ToVariant(withBase64: boolean=true): variant;
    /// save the public key as a .public json file
    // - i.e. a json containing all published properties of this instance
    // - persist ToVariant() as an human-readable JSON file
    function ToFile(const filename: TFileName): boolean;
    {$endif}
    /// low-level access to the binary buffer used ECC secp256r1 cryptography
    // - you should not use this property, but other methods
    property Content: TECCCertificateContent read fContent write fContent;
  published
    /// the TECCCertificate format version
    // - currently equals 1
    property Version: word read fContent.Version;
    /// the genuine identifier of this certificate, as hexadecimal text
    property Serial: RawUTF8 read GetSerial;
    /// identify the certificate issuer, as text
    property Issuer: RawUTF8 read GetIssuer;
    /// when this certificate was generated, as ISO-8601 text
    property IssueDate: RawUTF8 read GetIssueDate;
    /// valid not before this date, as ISO-8601 text
    property ValidityStart: RawUTF8 read GetValidityStart;
    /// valid not after this date, as ISO-8601 text
    property ValidityEnd: RawUTF8 read GetValidityEnd;
    /// hexadecimal text of the authority certificate identifier used for signing
    property AuthoritySerial: RawUTF8 read GetAuthoritySerial;
    /// identify the authoritify issuer used for signing, as text
    property AuthorityIssuer: RawUTF8 read GetAuthorityIssuer;
    /// if this certificate has been signed by itself
    // - a self-signed certificate will have its AuthoritySerial/AuthorityIssuer
    // fields matching Serial/Issuer, and should be used as "root" certificates
    property IsSelfSigned: boolean read GetIsSelfSigned;
    /// base-64 encoded text of the whole certificate binary information
    // - only the public part of the certificate will be shown: any private key
    // of a TECCCertificateSecret instance would be trimmed
    property Base64: RawUTF8 read PublicToBase64 write SetBase64;
  end;

  /// used to store a list of TECCCertificate instances
  // - e.g. in TECCCertificateChain.Items
  // - TJSONSerializer.RegisterObjArrayForJSON done in dddInfraApps and not
  // in this unit to avoid dependency to mORMot.pas
  TECCCertificateObjArray = array of TECCCertificate;

  /// a public/private certificate using ECC secp256r1 cryptography
  // - will store TECCCertificate public and associated private secret key
  // - implements a custom binary format, with validation period, and chaining
  // - could be used for safe data signing via SignToBase64/SignFile, and
  // authentication / key derivation
  // - allows optional anti-forensic diffusion during storage via AFSplitStripes
  TECCCertificateSecret = class(TECCCertificate)
  protected
    fPrivateKey: TECCPrivateKey;
    fAFSplitStripes: integer;
    function InternalLoad(const data: RawByteString): boolean; override;
    function InternalSave: RawByteString; override;
  public
    /// generate a new certificate, signed using the supplied Authority
    // - if Authority is nil, will generate a self-signed certificate
    // - the supplied Issuer name would be stored using AsciiToBaudot(),
    // truncated to the Issuer buffer size, i.e. 16 bytes - if Issuer is '',
    // TAESPRNG.Fill() will be used
    // - you may specify some validity time range, if needed
    // - default ParanoidVerify=true will validate the certificate digital
    // signature via a call ecdsa_verify() to ensure its usefulness
    // - would take around 4 ms under a 32-bit compiler, and 1 ms under 64-bit
    constructor CreateNew(Authority: TECCCertificateSecret; const IssuerText: RawUTF8='';
      ExpirationDays: integer=0; StartDate: TDateTime=0; ParanoidVerify: boolean=true);
    /// create a certificate with its private secret key from a password-protected
    // secure binary buffer
    // - perform all reverse steps from SaveToSecureBinary() method
    // - will raise an EECCException if the supplied Binary is incorrect
    constructor CreateFromSecureBinary(const Binary: RawByteString; const PassWord: RawUTF8;
      PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil); overload;
    /// create a certificate with its private secret key from a password-protected
    // secure binary buffer
    // - may be used on a constant array in executable, created via SaveToSource()
    // - perform all reverse steps from SaveToSecureBinary() method
    // - will raise an EECCException if the supplied Binary is incorrect
    constructor CreateFromSecureBinary(Data: pointer; Len: integer; const PassWord: RawUTF8;
      PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil); overload;
    /// create a certificate with its private secret key from an encrypted
    // secure .private binary file and its associated password
    // - perform all reverse steps from SaveToSecureFile() method
    // - will raise an EECCException if the supplied file is incorrect
    constructor CreateFromSecureFile(const FileName: TFileName; const PassWord: RawUTF8;
      PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil); overload;
    /// create a certificate with its private secret key from an encrypted
    // secure .private binary file stored in a given folder
    // - overloaded constructor retrieving the file directly from its folder
    // - perform all reverse steps from SaveToSecureFile() method
    // - will raise an EECCException if the supplied file is incorrect
    constructor CreateFromSecureFile(const FolderName: TFileName;
      const Serial, PassWord: RawUTF8; PBKDF2Rounds: integer=DEFAULT_ECCROUNDS;
      AES: TAESAbstractClass=nil); overload;
    /// finalize the instance, and safe erase fPrivateKey stored buffer
    destructor Destroy; override;
    /// returns TRUE if the private secret key is not filled with zeros
    function HasSecret: boolean;
    /// computes the 'Serial.private' file name of this certificate
    // - as used by SaveToSecureFile()
    function SaveToSecureFileName(FileNumber: integer=0): TFileName;
    /// backup the private secret key into an encrypted .private binary file
    // - you should keep all your private keys in a safe dedicated folder
    // - filename will be the certificate hexadecimal as 'Serial.private'
    // - will use anti-forensic diffusion of the private key (64 stripes = 2KB)
    // - then AES-256-CFB encryption (or the one specified in AES parameter) will
    // be performed from PBKDF2_HMAC_SHA256 derivation of an user-supplied password
    function SaveToSecureFile(const PassWord: RawUTF8; const DestFolder: TFileName;
      AFStripes: integer=64; PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil;
      NoHeader: boolean=false): boolean;
    /// backup the private secret key into several encrypted -###.private binary files
    // - secret sharing can be used to store keys at many different places, e.g.
    // on several local or remote drives, and therefore enhance privacy and safety
    // - it will use anti-forensic diffusion of the private key to distribute it
    // into pieces, in a manner that a subset of files can not regenerate the key:
    // as a result, a compromission of one sub-file won't affect the secret key
    // - filename will be the certificate hexadecimal as 'Serial-###.private'
    // - AES-256-CFB encryption (or the one specified in AES parameter) will be
    // performed from PBKDF2_HMAC_SHA256 derivation of an user-supplied password
    function SaveToSecureFiles(const PassWord: RawUTF8; const DestFolder: TFileName;
      DestFileCount: integer; AFStripes: integer=64; PBKDF2Rounds: integer=DEFAULT_ECCROUNDS;
      AES: TAESAbstractClass=nil; NoHeader: boolean=false): boolean;
    /// read a private secret key from an encrypted .private binary file
    // - perform all reverse steps from SaveToSecureFile() method
    // - returns TRUE on success, FALSE otherwise
    function LoadFromSecureFile(const FileName: TFileName; const PassWord: RawUTF8;
      PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil): boolean;
    /// backup the private secret key into an encrypted secure binary buffer
    // - you should keep all your private keys in a safe place
    // - will use anti-forensic diffusion of the private key (64 stripes = 2KB)
    // - then AES-256-CFB encryption (or the one specified in AES parameter) will
    // be performed from PBKDF2_HMAC_SHA256 derivation of an user-supplied password
    function SaveToSecureBinary(const PassWord: RawUTF8; AFStripes: integer=64;
      PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil; NoHeader: boolean=false): RawByteString;
    /// backup the private secret key into an encrypted source code constant
    // - may be used to integrate some private keys within an executable
    // - if ConstName='', _HEXASERIAL will be used, from 24 first chars of Serial
    // - the password may also be included as ConstName_PASS associated constant,
    // and as ConstName_CYPH in TSynPersistentWithPassword/TECCCertificateSecretSetting
    // encrypted format
    function SaveToSource(const ConstName, Comment, PassWord: RawUTF8;
      IncludePassword: boolean=true; AFStripes: integer=0; PBKDF2Rounds: integer=100;
      AES: TAESAbstractClass=nil; IncludeRaw: boolean=true): RawUTF8;
    /// read a private secret key from an encrypted secure binary buffer
    // - perform all reverse steps from SaveToSecureBinary() method
    // - returns TRUE on success, FALSE otherwise
    function LoadFromSecureBinary(const Binary: RawByteString; const PassWord: RawUTF8;
      PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil): boolean; overload;
    /// read a private secret key from an encrypted secure binary buffer
    // - perform all reverse steps from SaveToSecureBinary() method
    // - returns TRUE on success, FALSE otherwise
    function LoadFromSecureBinary(Data: pointer; Len: integer; const PassWord: RawUTF8;
      PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil): boolean; overload;
  public
    /// compute a base-64 encoded signature of some digital content
    // - memory buffer will be hashed using SHA-256, then will be signed using
    // ECDSA over the private secret key of this certificate instance
    // - you could later on verify this text signature according to the public
    // key of this certificate, calling TECCCertificateChain.IsSigned()
    // - create internally a temporary TECCSignatureCertified instance
    function SignToBase64(Data: pointer; Len: integer): RawUTF8; overload;
    /// compute a base-64 encoded signature of some digital content hash
    // - signature will be certified by private secret key of this instance
    // - you could later on verify this text signature according to the public
    // key of this certificate, calling TECCCertificateChain.IsSigned()
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - create internally a temporary TECCSignatureCertified instance
    function SignToBase64(const Hash: THash256): RawUTF8; overload;
    {$ifndef NOVARIANTS}
    /// compute a .sign digital signature of any file
    // - SHA-256/ECDSA digital signature is included in a JSON document
    // - you can set some additional metadata information for the "meta": field
    // - will raise an EECCException if FileToSign does not exist
    // - returns the .sign file name, which is in fact FileToSign+'.sign'
    // - use TECCSignatureCertifiedFile class to load and validate such files
    function SignFile(const FileToSign: TFileName;
      const MetaNameValuePairs: array of const): TFileName;
    {$endif}
    /// decrypt using the ECIES scheme, using this private certificate as key,
    // via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
    // - expects TECCCertificate.Crypt() cyphered content with its public key
    // - returns the decrypted content, or '' in case of failure
    // - optional shared information used for PBKDF2 or HMAC can be customized
    // - optionally, you can retrieve the sign-then-encrypt ECDSA secp256r1
    // signature and metadata stored in the header (to be checked via
    // TECCCertificateChain.IsSigned method), and/or the associated file timestamp
    function Decrypt(const Encrypted: RawByteString; out Decrypted: RawByteString;
      Signature: PECCSignatureCertifiedContent=nil; MetaData: PRawJSON=nil;
      FileDateTime: PDateTime=nil;
      const KDFSalt: RawUTF8='salt'; KDFRounds: integer=DEFAULT_ECCROUNDS;
      const MACSalt: RawUTF8='hmac'; MACRounds: integer=100): TECCDecrypt;
    /// decrypt using the ECIES scheme, using this private certificate as key,
    /// decrypt a file using the ECIES scheme, using this private certificate as
    // key, via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
    // - makes the reverse operation of TECCCertificate.EncryptFile method
    // - by default, will erase the (.synecc) extension to FileToDecrypt name
    // - optional salt information used for PBKDF2 can be customized, to unlock
    // the encryted file with the supplied password
    // - optionally, you can retrieve the sign-then-encrypt ECDSA secp256r1
    // signature stored in the header for TECCCertificateChain.IsSigned() in
    // supplied Signature^ and MetaData^ values
    function DecryptFile(const FileToDecrypt: TFileName; const DestFile: TFileName='';
      const Salt: RawUTF8='salt'; SaltRounds: integer=DEFAULT_ECCROUNDS;
      Signature: PECCSignatureCertifiedContent=nil; MetaData: PRawJSON=nil): TECCDecrypt;
  public
    /// how many anti-forensic diffusion stripes are used for private key storage
    // - default is 0, meaning no diffusion, i.e. 32 bytes of storage space
    // - you may set e.g. to 32 to activate safe diffusion to 1KB of storage
    // for ToBase64/SaveToStream methods
    // - is modified temporarly by SaveToSecure() method
    property AFSplitStripes: integer read fAFSplitStripes;
    /// disable private secret key storage in SaveToStream()
    // - default is false, i.e. the private secret key will be serialized
    // - you may set TRUE here so that SaveToStream() would store only the
    // public certificate, as expected by a TECCCertificate class
    // - is used e.g. by PublicToBase64 method to trim the private information
    property StoreOnlyPublicKey: boolean read fStoreOnlyPublicKey write fStoreOnlyPublicKey;
  end;

  /// store settings pointing to a local .private file containing a secret key
  // - following TECCCertificateSecret secure binary file format
  // - you may use "ECC infocrypt" command to retrieve SaveToSource constants
  TECCCertificateSecretSetting = class(TSynPersistentWithPassword)
  protected
    fSerial: RawUTF8;
    fFileName: TFileName;
    fPasswordRounds: integer;
  public
    /// initialize the settings with default values
    constructor Create; override;
    /// generate a TECCCertificateSecret instance corresponding to the settings
    // - is a wrapper around TECCCertificateSecret.CreateFromSecureFile
    // - will read the FileName file (if supplied), or search for the
    // <Serial>.private file in the supplied folder otherwise, then
    // use associated Password/PasswordRounds values to uncypher it
    // - returns nil if Serial and FileName are '', or raise an exception
    // on unexpected error
    // - caller is responsible of freeing the returned class instance
    function CertificateSecret(const FolderName: TFileName): TECCCertificateSecret;
  published
    /// the first characters of the .private file holding the secret key
    // - equals '' by default, meaning no private secret is defined
    // - you may use the FileName property instead to specify a full path name
    property Serial: RawUTF8 read fSerial write fSerial;
    /// the first characters of the .private file holding the secret key
    // - equals '' by default, meaning no private secret is defined
    // - you may use the Serial property instead to search in an application
    // specific folder
    property FileName: TFileName read fFileName write fFileName;
    /// the password used to protect the .private file
    // - matches the -authpass parameter used with "ECC decrypt" command, but
    // with TSynPersistentWithPassword encryption
    // - i.e. matches ConstName_CYPH as generated by TECCCertificateSecret.SaveToSource
    property Password: RawUTF8 read fPassword write fPassword;
    /// number of PBKDF2 rounds to be applied to the associated password
    // - matches ConstName_ROUNDS as generated by TECCCertificateSecret.SaveToSource
    // - matches the -authrounds parameter used with "ECC decrypt" command
    // - default is DEFAULT_ECCROUNDS, i.e. 60000
    property PasswordRounds: integer read fPasswordRounds write fPasswordRounds;
  end;

  /// store settings pointing to a local .private file containing a secret key
  // for .synecc file decryption
  // - following TECCCertificateSecret secure binary file format
  // - publishes Salt and SaltRounds values, as expected by
  // TECCCertificateSecret.Decrypt method
  TECCCertificateDecryptSetting = class(TECCCertificateSecretSetting)
  protected
    fSalt: RawUTF8;
    fSaltRounds: integer;
  public
    /// initialize the settings with default values
    constructor Create; override;
  published
    /// the Salt passphrase used to protect the .synecc encrypted file
    // - matches the -saltpass parameter used with "ECC crypt" command
    // - default is 'salt'
    property Salt: RawUTF8 read fSalt write fSalt;
    /// number of PBKDF2 rounds to be applied to the associated Salt
    // - matches the -saltrounds parameter used with "ECC crypt" command
    // - default is DEFAULT_ECCROUNDS, i.e. 60000
    property SaltRounds: integer read fSaltRounds write fSaltRounds;
  end;

  /// a ECDSA secp256r1 digital signature of some content, signed by an authority
  TECCSignatureCertified = class(TSynPersistent)
  protected
    fContent: TECCSignatureCertifiedContent;
    function GetAuthorityIssuer: RawUTF8;
    function GetAuthoritySerial: RawUTF8;
    function GetDate: RawUTF8;
  public
    /// initialize this signature
    constructor Create; override;
    /// compute a new signature of some digital content
    // - memory buffer will be hashed using SHA-256, then will be signed using
    // ECDSA over the private secret key of the supplied Authority certificate
    constructor CreateNew(Authority: TECCCertificateSecret;
      Data: pointer; Len: integer); overload;
    /// compute a new signature of some digital content hash
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - the hash will be signed using ECDSA over the private secret key of
    // the supplied Authority certificate
    constructor CreateNew(Authority: TECCCertificateSecret;
      const Hash: THash256); overload;
    /// initialize this signature from a supplied binary
    // - will raise an EECCException if the supplied binary content is incorrect
    constructor CreateFrom(const binary: TECCSignatureCertifiedContent;
      NoException: boolean=false);
    /// initialize this signature from a supplied base-64 encoded binary
    // - will raise an EECCException if the supplied base64 is incorrect
    constructor CreateFromBase64(const base64: RawUTF8;
      NoException: boolean=false);
    /// initialize this signature from the "sign": field of a JSON .sign file
    // - will raise an EECCException if the supplied file is incorrect
    constructor CreateFromFile(const signfilename: TFileName;
      NoException: boolean=false);
    /// fast check of the binary buffer storage of this signature
    // - performs basic checks, avoiding any void date, authority or signature
    // - use Verify() or TECCCertificateChain.IsSigned() methods for full
    // digital signature validation
    function Check: boolean;
    /// check if this digital signature matches a given data hash
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the supplied signing authority
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function Verify(Authority: TECCCertificate;
      const hash: THash256): TECCValidity; overload;
    /// check if this digital signature matches a given memory buffer
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the supplied signing authority
    // - will compute and verify the SHA-256 hash of the supplied data
    // - this method is thread-safe, and not blocking
    function Verify(Authority: TECCCertificate;
      Data: pointer; Len: integer): TECCValidity; overload;
    /// persist the signature as some base-64 encoded binary
    function ToBase64: RawUTF8;
    {$ifndef NOVARIANTS}
    /// returns a TDocVariant object of all published properties of this instance
    function ToVariant: variant; virtual;
    {$endif}
    /// retrieve the signature from some base-64 encoded binary
    // - returns true on success, false otherwise
    function FromBase64(const base64: RawUTF8): boolean;
    /// retrieve the signature from the "sign": field of a JSON .sign file
    // - returns true on success, false otherwise
    function FromFile(const signfilename: TFileName): boolean; virtual;
    /// save the ECDSA signature into a ASN.1's binary DER buffer
    // - note that DER content only stores the ECDSA digital signature, so
    // all certification information is lost
    function SaveToDERBinary: RawByteString;
    /// save the ECDSA signature into a ASN.1's binary DER file
    // - note that DER content only stores the ECDSA digital signature, so
    // all certification information is lost - consider using
    // TECCSignatureCertifiedFile instead
    // - returns TRUE on success, FALSE otherwise
    function SaveToDERFile(const FileName: TFileName): boolean;
    /// low-level access to the binary buffer used ECDSA secp256r1 cryptography
    // - you should not use this property, but other methods
    property Content: TECCSignatureCertifiedContent read fContent write fContent;
  published
    /// the TECCSignatureCertified format version
    // - currently equals 1
    property Version: word read fContent.Version;
    /// when this signature was generated, as ISO-8601 text
    property Date: RawUTF8 read GetDate;
    /// hexadecimal text of the authority certificate identifier used for signing
    property AuthoritySerial: RawUTF8 read GetAuthoritySerial;
    /// identify the authoritify issuer used for signing, as text
    property AuthorityIssuer: RawUTF8 read GetAuthorityIssuer;
  end;

  {$ifndef NOVARIANTS}
  /// handle a .sign file content as generated by TECCCertificateSecret.SignFile
  // - JSON document of a SHA-256/ECDSA secp256r1 digital signature
  TECCSignatureCertifiedFile = class(TECCSignatureCertified)
  protected
    fLowLevelInfo: TDocVariantData;
    fMD5Digest: TMD5Digest;
    fSHA256Digest: TSHA256Digest;
    fMetaData: variant;
    fSize: integer;
    fMD5: RawUTF8;
    fSHA256: RawUTF8;
  public
    /// create and set .sign fields after TECCCertificateSecret.Decrypt() process
    // - will compute Size, and MD5/SHA-256 hashes from aDecryptedContent
    // - will raise an EECCException if the supplied parameters are incorrect
    constructor CreateFromDecryptedFile(const aDecryptedContent: RawByteString;
      const Signature: TECCSignatureCertifiedContent; const MetaData: RawJSON);
    /// read a .sign digital signature file
    // - as previously generated by TECCCertificateSecret.SignFile
    // - will append '.sign' to aFileName, if it does not match this extension
    // - returns true on success, false otherwise
    function FromFile(const aFileName: TFileName): boolean; override;
    /// read a .sign digital signature JSON content
    // - as previously generated by TECCCertificateSecret.SignFile
    // - returns true on success, false otherwise
    function FromFileJson(const aFileContent: RawUTF8): boolean;
    /// compute .sign fields after TECCCertificateSecret.Decrypt() process
    // - will compute Size, and MD5/SHA-256 hashes from aDecryptedContent
    function FromDecryptedFile(const aDecryptedContent: RawByteString;
      const Signature: TECCSignatureCertifiedContent; const MetaData: RawJSON): boolean;
    /// low-level access to the whole JSON document members
    property LowLevelInfo: TDocVariantData read fLowLevelInfo;
    /// the MD5 binary hash as stored in the .sign file
    property MD5Digest: TMD5Digest read fMD5Digest;
    /// the SHA-256 binary hash as stored in the .sign file
    property SHA256Digest: TSHA256Digest read fSHA256Digest;
  published
    /// the meta data document as stored in the .sign file
    property MetaData: variant read fMetaData;
    /// the signed file size in bytes, as stored in the .sign file
    property Size: integer read fSize;
    /// the MD5 hexadecimal signature as stored in the .sign file
    property MD5: RawUTF8 read fMD5;
    /// the SHA-256 hexadecimal signature as stored in the .sign file
    property SHA256: RawUTF8 read fSHA256;
  end;
  {$endif NOVARIANTS}

  /// manage PKI certificates using ECC secp256r1 cryptography
  // - will implement a simple and efficient public-key infrastructure (PKI),
  // based on JSON objects or even plain base-64 encoded JSON strings
  // - consider using TECCCertificateChainFile from mORMot.pas if you want
  // to use convenient human-readable JSON serialization in files
  TECCCertificateChain = class(TSynPersistentLock)
  protected
    fItems: TECCCertificateObjArray;
    fIsValidCached: boolean;
    fIsValidCacheCount: integer;
    fIsValidCache: TInt64DynArray;
    function GetCount: integer;
    function InternalAdd(cert: TECCCertificate; expected: TECCValidity): integer;
    procedure SetIsValidCached(const Value: boolean);
    function IndexBySerial(const Serial: TECCCertificateID): integer;
  public
    /// initialize the certificate store from some JSON array of strings
    // - the serialization format is just a JSON array of base-64 encoded
    // certificates (with only public keys) - so diverse from CreateFromFile()
    // - will call LoadFromJson(), and raise EECCException on any error
    constructor CreateFromJson(const json: RawUTF8);
    /// initialize the certificate store from an array of base-64 encoded strings
    // - a TRawUTF8DynArray value is very convenient when storing the
    // certificates chain as part of JSON settings, e.g. TDDDAppSettings
    // - will call LoadFromArray(), and raise EECCException on any error
    constructor CreateFromArray(const values: TRawUTF8DynArray);
    /// finalize the certificate store
    destructor Destroy; override;
    /// delete all stored certificates
    // - this method is thread-safe, calling Safe.Lock/Unlock
    procedure Clear;
    /// search for a certificate from its hexadecimal text identifier
    // - this method is not thread-safe, unless you use Safe.Lock/Unlock
    function GetBySerial(const Serial: RawUTF8): TECCCertificate; overload;
    /// search for a certificate from its binary identifier
    // - this method is not thread-safe, unless you use Safe.Lock/Unlock
    function GetBySerial(const Serial: TECCCertificateID): TECCCertificate; overload;
    /// search for a certificate binary content from its binary identifier
    // - returns TRUE if the Serial identifier was found, FALSE otherwise
    // - this method is thread-safe, since it will make a private copy of the content
    function GetBySerial(const Serial: TECCCertificateID;
      out Content: TECCCertificateContent): boolean; overload;
    /// search for a certificate public key from its binary identifier
    // - returns TRUE if the Serial identifier was found, FALSE otherwise
    // - this method is thread-safe, since it will make a private copy of the key
    function GetBySerial(const Serial: TECCCertificateID;
      out PublicKey: TECCPublicKey): boolean; overload;
    /// check if the certificate is valid, against known certificates chain
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - consider setting IsValidCached property to TRUE to reduce resource use
    // - this method is thread-safe, and not blocking
    function IsValid(cert: TECCCertificate): TECCValidity; overload;
    /// check if the certificate is valid, against known certificates chain
    // - will check internal properties of the certificate (e.g. validity dates,
    // unless ignoreDate=TRUE), and validate the stored ECDSA signature
    // according to the public key of the associated signing authority (which
    // should be valid, and stored in Items[])
    // - consider setting IsValidCached property to TRUE to reduce resource use
    // - this method is thread-safe, and not blocking
    function IsValid(const content: TECCCertificateContent;
      ignoreDate: boolean=false): TECCValidity; overload;
    /// check all stored certificates and their authorization chain
    // - returns nil if all items were valid
    // - returns the list of any invalid instances
    // - do not free the returned items, since they are reference to Items[]
    function ValidateItems: TECCCertificateObjArray;
    /// check if the digital signature is recognized by the stored certificates
    // - will check that sign.AuthoritySerial is part of the Items[] list
    // - this method won't perform the ECDSA verification: use IsSigned() instead
    // - this method is thread-safe, and not blocking
    function IsAuthorized(sign: TECCSignatureCertified): boolean; overload;
    /// check if the digital signature is recognized by the stored certificates
    // - will check that sign.AuthoritySerial is part of the Items[] list
    // - this method won't perform the ECDSA verification: use IsSigned() instead
    // - this method is thread-safe, and not blocking
    function IsAuthorized(const sign: TECCSignatureCertifiedContent): boolean; overload;
    /// check if the digital signature is recognized by the stored certificates
    // - will check that the supplied base64 encoded text is a ECC signature,
    // and that its AuthoritySerial is part of the Items[] list
    // - this method won't perform the ECDSA verification: use IsSigned() instead
    // - this method is thread-safe, and not blocking
    function IsAuthorized(const base64sign: RawUTF8): boolean; overload;
    /// check if the digital signature of a given data hash is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function IsSigned(sign: TECCSignatureCertified; const hash: THash256): TECCValidity; overload;
    /// check if the digital signature of a given memory buffer is valid
    // - if sign is a TECCSignatureCertifiedFile, the Size, MD5 and SHA256 fields
    // stored in the .sign file content will be checked against the supplied data
    // before ECDSA signature, and would return ecvCorrupted on error
    // - it will then check internal properties of the certificate (e.g. validity
    // dates), and validate the stored SHA-256/ECDSA signature according to the
    // public key of the associated signing authority (stored in Items[])
    // - this method is thread-safe, and not blocking
    function IsSigned(sign: TECCSignatureCertified; Data: pointer; Len: integer): TECCValidity; overload;
    {$ifndef NOVARIANTS}
    /// check if the digital signature file (.sign content) is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - will use TECCSignatureCertifiedFile Size, MD5 and SHA256 fields,
    // so could be used without any actual memory buffer
    // - this method is thread-safe, and not blocking
    function IsSigned(sign: TECCSignatureCertifiedFile): TECCValidity; overload;
    {$endif NOVARIANTS}
    /// check if the digital signature of a given data hash is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function IsSigned(const sign: TECCSignatureCertifiedContent;
      const hash: THash256): TECCValidity; overload;
    /// check if the digital signature of a given memory buffer is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - will compute and verify the SHA-256 hash of the supplied data
    // - this method is thread-safe, and not blocking
    function IsSigned(const sign: TECCSignatureCertifiedContent;
      Data: pointer; Len: integer): TECCValidity; overload;
    /// verify the base-64 encoded digital signature of a given hash
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function IsSigned(const base64sign: RawUTF8;
      const hash: THash256): TECCValidity; overload;
    /// verify the base-64 encoded digital signature of a given memory buffer
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - will compute and verify the SHA-256 hash of the supplied data
    // - this method is thread-safe, and not blocking
    function IsSigned(const base64sign: RawUTF8;
      Data: pointer; Len: integer): TECCValidity; overload;
    /// register a certificate in the internal certificate chain
    // - returns the index of the newly inserted certificate
    // - returns -1 on error, e.g. if the certificate was not valid, or its
    // serial was already part of the internal list
    // - any self-signed certificate will be rejected: use AddSelfSigned() instead
    // - this method is thread-safe
    function Add(cert: TECCCertificate): integer;
    /// register a self-signed certificate in the internal certificate chain
    // - a self-signed certificate will have its AuthoritySerial/AuthorityIssuer
    // fields matching Serial/Issuer, and should be used as "root" certificates
    // - returns -1 on error, e.g. if the certificate was not valid,
    // not self-signed or its serial was already part of the internal list
    // - this method is thread-safe
    function AddSelfSigned(cert: TECCCertificate): integer;
    /// save the whole certificates chain as an array of base-64 encoded content
    // - each certificate would be stored via PublicToBase64() into a RawUTF8
    // - any private key would be trimmed from the output: private secret keys
    // should NOT be kept in the main chain, in which only public keys will appear
    function SaveToArray: TRawUTF8DynArray;
    /// load a certificates chain from an array of base-64 encoded content
    // - follows SaveToArray format
    // - would create only TECCCertificate instances with their public keys,
    // since no private key, therefore no TECCCertificateSecret is expected
    function LoadFromArray(const values: TRawUTF8DynArray): boolean;
    /// save the whole certificates chain as a JSON array
    // - each certificate would be stored via PublicToBase64() into a JSON string
    // - any private key would be trimmed from the output JSON: private secret
    // keys should NOT be kept in the main chain, in which only public keys
    // should appear
    function SaveToJson: RawUTF8;
    /// load a certificates chain from a JSON array of strings
    // - follows SaveToJson format, i.e. base-64 encoded strings
    // - would create only TECCCertificate instances with their public keys,
    // since no private key, therefore no TECCCertificateSecret is expected
    function LoadFromJson(const json: RawUTF8): boolean;
  {$ifndef NOVARIANTS}
  public
    /// initialize the certificate store from some JSON-serialized .ca file
    // - the file would store plain verbose information of all certificates,
    // i.e. base-64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, this file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    // - you may use SaveToFile() method to create such JSON file
    // - will call LoadFromFile(), and raise EECCException on any error
    constructor CreateFromFile(const jsonfile: TFileName);
    /// initialize the certificate store from an array of .public file names
    // - raise EECCException on any error when reading a .public file
    constructor CreateFromFiles(const files: array of TFileName);
    /// save the whole certificates chain as a JSON object, matching .ca format
    // - is in fact the human-friendly JSON serialization of this instance
    // - would store plain verbose information of all certificates,
    // i.e. base-64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, .ca file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    function SaveToFileVariant: variant;
    /// save the whole certificates chain as a JSON content, matching .ca format
    // - is in fact the human-friendly JSON serialization of this instance
    // - would store plain verbose information of all certificates,
    // i.e. base-64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, .ca file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    function SaveToFileContent: RawUTF8;
    /// load a certificates chain from some JSON-serialized .ca file content
    // - you may use SaveToFileContent method to create such JSON content
    // - would create only TECCCertificate instances with their public keys,
    // since no private key, therefore no TECCCertificateSecret is expected
    function LoadFromFileContent(const cajsoncontent: RawUTF8): boolean;
    /// save the whole certificates chain as a .ca JSON file
    // - is in fact the human-friendly JSON serialization of this instance
    // - the .ca file would store plain verbose information of all certificates,
    // i.e. base-64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, this file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    function SaveToFile(const jsonfile: TFileName): boolean;
    /// load a certificates chain from some JSON-serialized .ca file
    // - you may use SaveToFile() method to create such JSON file
    // - would create only TECCCertificate instances with their public keys,
    // since no private key, therefore no TECCCertificateSecret is expected
    // - if jsonfile is not in the current folder, will try ECCKeyFileFolder
    function LoadFromFile(const jsonfile: TFileName): boolean;
  {$endif NOVARIANTS}
  {$ifndef DELPHI5OROLDER}
  published
  {$endif}
    /// low-level access to the internal certificates chain
    // - thread-safe process may be done using
    // ! Safe.Lock; try ... finally Safe.Unlock; end;
    property Items: TECCCertificateObjArray read fItems;
    /// how many certificates are currently stored in the certificates chain
    property Count: integer read GetCount;
    /// if the IsValid() calls should maintain a cache of all valid certificates
    // - will use a naive but very efficient crc64c hashing of previous contents
    // - since ecdsa_verify() is very demanding, such a cache may have a huge
    // speed benefit if the certificates are about to be supplied several times
    // - is disabled by default, for paranoid safety
    property IsValidCached: boolean read fIsValidCached write SetIsValidCached;
  end;

  /// abstract ECDHE secure protocol with unilateral or mutual authentication
  // - inherited TECDHEProtocolClient and TECDHEProtocolServer
  // classes will implement a secure client/server transmission, with a one-way
  // handshake and asymmetric encryption
  // - will validate ECDSA signatures using certificates of the associated PKI
  // - will create an ephemeral ECC key pair for perfect forward security
  // - will use ECDH to compute a shared ephemeral session on both sides,
  // for AES-128 or AES-256 encryption, and HMAC with anti-replay - default
  // algorithm will use fast and safe AES-CFB 128-bit encryption, with efficient
  // AES-CRC 256-bit MAC, and full hardware accelleration on Intel CPUs
  TECDHEProtocol = class(TInterfacedObjectLocked, IProtocol)
  protected
    fPKI: TECCCertificateChain;
    fPrivate: TECCCertificateSecret;
    fAlgo: TECDHEAlgo;
    fEFSalt: RawByteString;
    fMACSalt: RawByteString;
    fOwned: set of (ownPKI, ownPrivate);
    fCertificateValidity: TECCValidity;
    fRndA,fRndB: THash128;
    fAES: array[boolean] of TAESAbstract;
    fkM: array[boolean] of THash256Rec;
    // contains inc(PInt64(@aKey)^) to maintain RX/TX sequence number
    procedure SetKey(aEncrypt: boolean);
    procedure ComputeMAC(aEncrypt: boolean; aEncrypted: pointer; aLen: integer;
      out aMAC: THash256Rec);
    function Verify(frame: PByteArray; len: integer; const QC: TECCCertificateContent;
      out res: TProtocolResult): boolean;
    procedure Sign(frame: PByteArray; len: integer; out QC: TECCCertificateContent);
    procedure SharedSecret(sA,sB: PHash256);
  public
    /// initialize the ECDHE protocol with a PKI and a private secret key
    // - if aPKI is not set, the certificates won't be validated and the protocol
    // will allow self-signed credentials
    // - aPrivate should always be set for mutual or unilateral authentication
    // - will implement unilateral authentication if aPrivate=nil for this end
    constructor Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain;
      aPrivate: TECCCertificateSecret); reintroduce; overload; virtual;
    /// will create another instance of this communication protocol
    constructor CreateFrom(aAnother: TECDHEProtocol); virtual;
    /// initialize the communication by exchanging some client/server information
    // - this method should be overriden with the proper implementation
    function ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult; virtual; abstract;
    /// creates a new TECDHEProtocolClient or TECDHEProtocolServer from a text key
    // - expected layout is values separated by ; with at least a=... pair
    // - if needed, you can specify p=... as the password file name (searching
    // for first matching unique file name with .private extension in the
    // current directory of in ECCKeyFileFolder), and pw=...;pr=... for the
    // associated password protection (password content and rounds)
    // - optional ca=..;a=..;k=..;e=..;m=.. switches will match PKI, Auth, KDF,
    // EF and MAC properties of this class instance (triming left lowercase chars)
    // - global value set by FromKeySetCA() is used as PKI, unless ca=.. is set
    // (as a .ca file name, or as ca=base64,base64 or ca="base64","base64")
    // - a full text key with default values may be:
    // $ a=mutual;k=hmacsha256;e=aescrc128;m=duringef;p=34a2;pw=passwordFor34a2;
    // $ pr=60000;ca=websockets
    // - returns nil if aKey does not match this format, i.e. has no p=..,pw=..
    class function FromKey(const aKey: RawUTF8; aServer: boolean): TECDHEProtocol;
    /// defines the default PKI instance to be used by FromKey
    // - used if the ca=... property is not set in the aKey value
    class procedure FromKeySetCA(aPKI: TECCCertificateChain);
    /// computes a TSynPersistentWithPassword key expected by FromKey
    // - the .private key file name, and its associated password/rounds should
    // be specified, but for unilateral authentication on the other side
    // - pki should be a .ca file name, 'base64,base64' or '"base64","base64"'
    // - result of this method can be stored directly in a .settings file,
    // to enable the TECDHEProtocol safe protocol for transmission
    class function FromKeyCompute(const privkey, privpassword: RawUTF8;
      privrounds: integer=DEFAULT_ECCROUNDS; const pki: RawUTF8=''; auth: TECDHEAuth=authMutual;
      kdf: TECDHEKDF=kdfHmacSha256; ef: TECDHEEF=efAesCrc128;
      mac: TECDHEMAC=macDuringEF; customkey: cardinal=0): RawUTF8;
    /// finalize the instance
    // - also erase all temporary secret keys, for safety
    destructor Destroy; override;
    /// encrypt a message on one side, ready to be transmitted to the other side
    // - will use the Encryption Function EF, according to the shared secret key
    // - this method is thread-safe
    procedure Encrypt(const aPlain: RawByteString; out aEncrypted: RawByteString); virtual;
    /// decrypt a message on one side, as transmitted from the other side
    // - will use the Encryption Function EF, according to the shared secret key
    // - returns sprInvalidMAC in case of wrong aEncrypted input (e.g. packet
    // corruption, MiM or Replay attacks attempts)
    // - this method is thread-safe
    function Decrypt(const aEncrypted: RawByteString; out aPlain: RawByteString): TProtocolResult; virtual;
    /// check for any transmission error of the supplied encrypted text
    // - returns sprSuccess if the stored CRC of the encrypted flow matches
    // - returns sprInvalidMAC in case of wrong aEncrypted input
    // - is only implemented for MAC=macDuringEF, otherwise returns sprUnsupported
    // - to be called before Decrypt(), since this later method will change the
    // internal kM[false] sequence number
    function CheckError(const aEncrypted: RawByteString): TProtocolResult; virtual;
    /// will create another instance of this communication protocol
    function Clone: IProtocol;
    /// shared public-key infrastructure, used to validate exchanged certificates
    // - will be used for authenticity validation of ECDSA signatures
    property PKI: TECCCertificateChain read fPKI;
    /// the current Authentication scheme
    // - this value on client side should match server's Authorized
    // - this value on server side may change if the client forced another mode
    property Auth: TECDHEAuth read fAlgo.auth;
    /// the current Key Derivation Function
    // - this value should match on both client and server sides
    property KDF: TECDHEKDF read fAlgo.kdf write fAlgo.kdf;
    /// the current salt, used by the Key Derivation Function KDF to compute the
    // key supplied to the Encryption Function EF
    // - equals 'ecdhesalt' by default
    // - this value should match on both client and server sides
    property EFSalt: RawByteString read fEFSalt write fEFSalt;
    /// the current Encryption Function
    // - this value should match on both client and server sides
    property EF: TECDHEEF read fAlgo.ef write fAlgo.ef;
    /// the current salt, used by the Key Derivation Function KDF to compute the
    // key supplied to the Message Authentication Code MAC
    // - equals 'ecdhemac' by default
    // - this value should match on both client and server sides
    property MACSalt: RawByteString read fMACSalt write fMACSalt;
    /// the current Message Authentication Code
    // - this value should match on both client and server sides
    property MAC: TECDHEMAC read fAlgo.mac write fAlgo.mac;
    /// after handshake, contains the information about the other side
    // public key certificate validity, against the shared PKI
    property CertificateValidity: TECCValidity read fCertificateValidity;
  end;

  /// meta-class of the TECDHEProtocol type
  TECDHEProtocolClass = class of TECDHEProtocol;

  /// implements ECDHE secure protocol on client side
  TECDHEProtocolClient = class(TECDHEProtocol)
  protected
    fdE: TECCPrivateKey;
  public
    /// initialize the ECDHE protocol on the client side
    // - will check that aAuth is compatible with the supplied aPKI/aPrivate
    constructor Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain;
      aPrivate: TECCCertificateSecret); override;
    /// generate the authentication frame sent from the client
    procedure ComputeHandshake(out aClient: TECDHEFrameClient);
    /// validate the authentication frame sent back by the server
    function ValidateHandshake(const aServer: TECDHEFrameServer): TProtocolResult;
    /// initialize the client communication
    // - if MsgIn is '', will call ComputeHandshake
    // - if MsgIn is set, will call ValidateHandshake
    function ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult; override;
  end;

  /// implements ECDHE secure protocol on server side
  TECDHEProtocolServer = class(TECDHEProtocol)
  protected
    fAuthorized: TECDHEAuths;
  public
    /// initialize the ECDHE protocol on the client side
    // - will check that aAuth is compatible with the supplied aPKI/aPrivate
    constructor Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain;
      aPrivate: TECCCertificateSecret); override;
    /// will create another instance of this communication protocol
    constructor CreateFrom(aAnother: TECDHEProtocol); override;
    /// generate the authentication frame corresponding to the client request
    // - may change Auth property if the Client requested another authentication
    // scheme, allowed in Authorized setting and compatible with fPrivate
    function ComputeHandshake(const aClient: TECDHEFrameClient;
      out aServer: TECDHEFrameServer): TProtocolResult;
    /// initialize the server communication
    // - will call ComputeHandshake
    function ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult; override;
    /// the Authentication Schemes allowed by this server
    // - by default, only the aAuth value specified to Create is allowed
    // - you can set e.g. [authMutual,authServer] for a weaker pattern
    property Authorized: TECDHEAuths read fAuthorized write fAuthorized;
  end;

{$ifndef NOVARIANTS}

  /// implements JSON Web Tokens using 'ES256' algorithm
  // - i.e. ECDSA using the P-256 curve and the SHA-256 hash algorithm
  // - as defined in http://tools.ietf.org/html/rfc7518 paragraph 3.4
  // - since ECDSA signature and verification is CPU consumming (under x86, it
  // takes 2.5 ms, but only 0.3 ms on x64) you may enable CacheTimeoutSeconds
  TJWTES256 = class(TJWTAbstract)
  protected
    fCertificate: TECCCertificate;
    fOwnCertificate: boolean;
    function ComputeSignature(const headpayload: RawUTF8): RawUTF8; override;
    procedure CheckSignature(const headpayload: RawUTF8; const signature: RawByteString;
      var JWT: TJWTContent); override;
  public
    /// initialize the JWT processing instance using ECDSA P-256 algorithm
    // - the supplied set of claims are expected to be defined in the JWT payload
    // - the supplied ECC certificate should be a TECCCertificate storing the
    // public key needed for Verify(), or a TECCCertificateSecret storing also
    // the private key required by Compute()
    // - aCertificate is owned by this instance if property OwnCertificate is true
    // - aAudience are the allowed values for the jrcAudience claim
    // - aExpirationMinutes is the deprecation time for the jrcExpirationTime claim
    // - aIDIdentifier and aIDObfuscationKey are passed to a
    // TSynUniqueIdentifierGenerator instance used for jrcJwtID claim
    constructor Create(aCertificate: TECCCertificate; aClaims: TJWTClaims;
      const aAudience: array of RawUTF8; aExpirationMinutes: integer=0;
      aIDIdentifier: TSynUniqueIdentifierProcess=0; aIDObfuscationKey: RawUTF8=''); reintroduce;
    /// finalize the instance
    destructor Destroy; override;
    /// access to the associated TECCCertificate instance
    // - which may be a TECCCertificateSecret for Compute() private key
    property Certificate: TECCCertificate read fCertificate;
    /// if the associated TECCCertificate is to be owned by this instance
    property OwnCertificate: boolean read fOwnCertificate write fOwnCertificate;
  end;

{$endif NOVARIANTS}

const
  /// file extension of the JSON file storing a TECCCertificate public key
  ECCCERTIFICATEPUBLIC_FILEEXT = '.public';
  /// file extension of the binary encrypted file storing a private key
  // - as generated by TECCCertificateSecret.SaveToSecureFile method
  ECCCERTIFICATESECRET_FILEEXT = '.private';
  /// file extension of the JSON file storing a digital signature of a file
  // - by convention, this .sign extension is appended to the original file name
  // - as generated by TECCCertificateSecret.SignFile, and loaded by the
  // TECCSignatureCertifiedFile class
  ECCCERTIFICATESIGN_FILEEXT = '.sign';
  /// file extension of the JSON file storing a certificate authorities chain
  // - as generated by mORMot.pas TECCCertificateChainFile.SaveToFile()
  // and loaded by TECCCertificateChain.LoadFromFile
  ECCCERTIFICATES_FILEEXT = '.ca';
  /// file extension of the ECIES encrypted file
  // - with optional digital signature of the plain content
  // - as generated by TECCCertificate.Encrypt/EncryptFile, and decoded via
  // TECCCertificateSecret.Decrypt
  ENCRYPTED_FILEEXT = '.synecc';

/// search the single .public or .private file starting with the supplied file name
// - as used in the ECC.dpr command-line sample project
// - returns true and set the full file name of the matching file
// - returns false is there is no match, or more than one matching file
// - will also search in ECCKeyFileFolder, if the supplied folder is not enough
function ECCKeyFileFind(var TruncatedFileName: TFileName; privkey: boolean): boolean;

/// search the single .public or .private file used to crypt a given content
// - match the format generated by TECCCertificate.Encrypt/EncryptFile
// - returns true on success, false otherwise
// - will also search in ECCKeyFileFolder, if the current folder is not enough
function ECIESKeyFileFind(const encrypted: RawByteString; out keyfile: TFileName;
  privkey: boolean=true): boolean;

/// retrieve the private local folder used to store .public or .private files
// - it is better to store all you key files in a single place, for easier
// and safer management
// - under Windows, returns 'C:\Users\username\AppData\Local\Synopse\Keys\'
// - under Linux, returns '$HOME/.synopse/keys/'
function ECCKeyFileFolder: TFileName;


function ToText(val: TECCValidity): PShortString; overload;
function ToText(res: TECCDecrypt): PShortString; overload;
function ToText(algo: TECIESAlgo): PShortString; overload;
function ToText(algo: TECDHEAuth): PShortString; overload;
function ToText(algo: TECDHEKDF): PShortString; overload;
function ToText(algo: TECDHEEF): PShortString; overload;
function ToText(algo: TECDHEMAC): PShortString; overload;


implementation


{ *********** low-level ECC secp256r1 ECDSA and ECDH functions *********** }

function getRandomNumber(out dest: THash256): integer;
{$ifdef ECC_STATICLIB_AVAILABLE} cdecl;
  {$ifdef FPC}public name{$ifdef Win32}'_getRandomNumber'{$else}'getRandomNumber'{$endif};{$endif}
 {$endif}
begin
  TAESPRNG.Fill(dest);
  result := 1;
end;

{
  Benchmark of all available x86/32-bit variants, compiled with MinGW-W64 5.2.0

  gcc -g -O2 -c ecc.c
  d:\dev\tools\objconv.exe -fomf -nd -nu- ecc.o
  del eccwin32O2.o
  ren ecc.o SynEccWin32O2.o
  del eccwin32O2.obj
  ren ecc.obj SynEccWin32O2.obj

  Win32 ECC_32ASM
  - ecc_make_key: 1,000 assertions passed  2.38s
  - ecdsa_sign: 1,000 assertions passed  2.44s
  - ecdsa_verify: 1,000 assertions passed  2.96s
  - ecdh_shared_secret: 2,997 assertions passed  5.08s
  Total failed: 0 / 5,997  - ECC cryptography PASSED  12.88s

  Linux32 (Kylix) ECC_32ASM
  - ecc_make_key: 1,000 assertions passed  2.36s
  - ecdsa_sign: 1,000 assertions passed  2.44s
  - ecdsa_verify: 1,000 assertions passed  2.95s
  - ecdh_shared_secret: 2,997 assertions passed  5.07s
  Total failed: 0 / 5,997  - ECC cryptography PASSED  12.84s

  Win32 ECC_O1 (eccwin32O1.obj = 10480 bytes)
  - ecc_make_key: 1,000 assertions passed  2.34s
  - ecdsa_sign: 1,000 assertions passed  2.42s
  - ecdsa_verify: 1,000 assertions passed  2.91s
  - ecdh_shared_secret: 2,997 assertions passed  4.98s
  Total failed: 0 / 5,997  - ECC cryptography PASSED  12.67s

  Win32 ECC_O2 (eccwin32O2.obj = 16700 bytes)
  - ecc_make_key: 1,000 assertions passed  2.16s
  - ecdsa_sign: 1,000 assertions passed  2.20s
  - ecdsa_verify: 1,000 assertions passed  2.66s
  - ecdh_shared_secret: 2,997 assertions passed  4.58s
  Total failed: 0 / 5,997  - ECC cryptography PASSED  11.63s

  Win32 ECC_O3 (eccwin32O3.obj = 66798 bytes)
  - ecc_make_key: 1,000 assertions passed  2.17s
  - ecdsa_sign: 1,000 assertions passed  2.20s
  - ecdsa_verify: 1,000 assertions passed  2.65s
  - ecdh_shared_secret: 2,997 assertions passed  4.59s
  Total failed: 0 / 5,997  - ECC cryptography PASSED  11.64s

  -> conclusion: under Win32, ECC_O2 is used, and ECC_32ASM for Kylix+FPC
     time is around 2-3 ms for each operation (i.e. 400-500/sec)


  Benchmark of all available x64/64-bit variants, compiled with MinGW-W64 5.2.0

  Win64 ECC_O1 (eccwin64O1.o = 45765 bytes)
  - ecc_make_key: 1,000 assertions passed  601.37ms
  - ecdsa_sign: 1,000 assertions passed  622.23ms
  - ecdsa_verify: 1,000 assertions passed  758.28ms
  - ecdh_shared_secret: 2,997 assertions passed  1.26s
  Total failed: 0 / 5,997  - ECC cryptography PASSED  3.32s

  Win64 ECC_O2 (eccwin64O2.o = 84779 bytes)
  - ecc_make_key: 1,000 assertions passed  573.09ms
  - ecdsa_sign: 1,000 assertions passed  588.86ms
  - ecdsa_verify: 1,000 assertions passed  712.31ms
  - ecdh_shared_secret: 2,997 assertions passed  1.20s
  Total failed: 0 / 5,997  - ECC cryptography PASSED  3.16s

  Win64 ECC_O3 (eccwin64O3.o = 204775 bytes)
  - access violation at startup (due to .o linking error by Delphi)

  -> conclusion: under Win64, ECC_O2 is used
     time is around 0.5-0.6 ms for each operation (i.e. 2000/sec)
     x64 is four time faster than x86 for such arithmetic tasks :)

}

{$ifdef ECC_STATICLIB_AVAILABLE}

{$ifdef ECC_32ASM}

{$I SynEcc32asm.inc}

{$else}

{$ifdef CPUX86}
  {$ifdef FPC}
    {$ifdef MSWINDOWS}
      {$ifdef ECC_O1}
        {$L static\i386-win32\eccwin32O1.o}
      {$endif}
      {$ifdef ECC_O2}
        {$L static\i386-win32\eccwin32O2.o}
      {$endif}
      {$ifdef ECC_O3}
        {$L static\i386-win32\eccwin32O3.o}
      {$endif}
    {$else}
      {$ifdef ECC_O1}
        {$L static/i386-linux/ecclin32O1.o}
      {$endif}
      {$ifdef ECC_O2}
        {$L static/i386-linux/ecclin32O2.o}
      {$endif}
      {$ifdef ECC_O3}
        {$L static/i386-linux/ecclin32O3.o}
      {$endif}
    {$endif MSWINDOWS}
  {$else}
    {$ifdef ECC_O1}
      {$L SynEcc32O1.obj}
    {$endif}
    {$ifdef ECC_O2}
      {$L SynEcc32O2.obj}
    {$endif}
    {$ifdef ECC_O3}
      {$L SynEcc32O3.obj}
    {$endif}
  {$endif FPC}
{$endif CPUX86}

{$ifdef CPUX64}
  {$ifdef MSWINDOWS}
    // same .o format under Win64 for Delphi and FPC :)
    {$ifdef ECC_O1}
      {$L static\x86_64-win64\eccwin64O1.o}
    {$endif}
    {$ifdef ECC_O2}
      {$ifdef FPC}
      {$L static\x86_64-win64\eccwin64O2.o}
      {$else}
      {$L SynEcc64O2.o} // same file as static\x86_64-win64\eccwin64O2.o
      {$endif}
    {$endif}
    {$ifdef ECC_O3}
      {$L static\x86_64-win64\eccwin64O3.o}
    {$endif}
  {$else}
  {$ifdef FPC}
    {$ifdef ECC_O1}
      {$L static/x86_64-linux/ecclin64O1.o}
    {$endif}
    {$ifdef ECC_O2}
      {$L static/x86_64-linux/ecclin64O2.o}
    {$endif}
    {$ifdef ECC_O3}
      {$L static/x86_64-linux/ecclin64O3.o}
    {$endif}
  {$endif FPC}
  {$endif MSWINDOWS}
{$endif CPUX64}

function ecc_make_key; external;
function ecdh_shared_secret; external;
function ecdsa_sign; external;
function ecdsa_verify; external;

{$endif ECC_32ASM}

{$else ECC_STATICLIB_AVAILABLE}

// currently no .o file available under ARM/PPC -> stub calls of pascal functions

function ecc_make_key(out pub: TECCPublicKey; out priv: TECCPrivateKey): boolean;
begin
  result := ecc_make_key_pas(pub, priv);
end;

function ecdh_shared_secret(const pub: TECCPublicKey; const priv: TECCPrivateKey;
  out secret: TECCSecretKey): boolean;
begin
  result := ecdh_shared_secret_pas(pub, priv, secret);
end;

function ecdsa_sign(const priv: TECCPrivateKey; const hash: TECCHash;
  out sign: TECCSignature): boolean;
begin
  result := ecdsa_sign_pas(priv, hash, sign);
end;

function ecdsa_verify(const pub: TECCPublicKey; const hash: TECCHash;
  const sign: TECCSignature): boolean;
begin
  result := ecdsa_verify_pas(pub, hash, sign);
end;

{$endif ECC_STATICLIB_AVAILABLE}


{ Pure Pascal Version of low-level ECC process (adapted from micro-ecc.c code)

 Some numbers (on another slower computer than the previous values above),
 which is quite acceptable, since it is faster than gcc -O1 mode :)
 (of course, UInt128 support in gcc -O2 is still preferred on x86_64)

Delphi 7 pascal
  - ecc_make_key: 1,000 assertions passed  2.75s
  - ecdsa_sign: 1,000 assertions passed  2.80s
  - ecdsa_verify: 1,000 assertions passed  3.39s
  - ecdh_shared_secret: 2,997 assertions passed  5.68s
  Total failed: 0 / 529,825  - ECC cryptography PASSED  18.77s

Delphi 7 ECC_32ASM
  - ecc_make_key: 1,000 assertions passed  2.86s
  - ecdsa_sign: 1,000 assertions passed  2.92s
  - ecdsa_verify: 1,000 assertions passed  3.75s
  - ecdh_shared_secret: 2,997 assertions passed  6.11s
  Total failed: 0 / 529,825  - ECC cryptography PASSED  19.89s

FPC Win32 pascal
  - ecc_make_key: 1,000 assertions passed  2.78s
  - ecdsa_sign: 1,000 assertions passed  2.85s
  - ecdsa_verify: 1,000 assertions passed  3.46s
  - ecdh_shared_secret: 2,997 assertions passed  5.89s
  Total failed: 0 / 529,825  - ECC cryptography PASSED  19.72s

Delphi 7 ECC_O2
  - ecc_make_key: 1,000 assertions passed  2.58s
  - ecdsa_sign: 1,000 assertions passed  2.64s
  - ecdsa_verify: 1,000 assertions passed  3.19s
  - ecdh_shared_secret: 2,997 assertions passed  5.43s
  Total failed: 0 / 529,825  - ECC cryptography PASSED  17.86s

Delphi XE7-32 pascal
  - ecc_make_key: 1,000 assertions passed  2.74s
  - ecdsa_sign: 1,000 assertions passed  2.79s
  - ecdsa_verify: 1,000 assertions passed  3.40s
  - ecdh_shared_secret: 2,997 assertions passed  5.84s
  Total failed: 0 / 529,825  - ECC cryptography PASSED  18.82s

Delphi XE7-32 ECC_O2
  - ecc_make_key: 1,000 assertions passed  2.43s
  - ecdsa_sign: 1,000 assertions passed  2.52s
  - ecdsa_verify: 1,000 assertions passed  3.06s
  - ecdh_shared_secret: 2,997 assertions passed  5.27s
  Total failed: 0 / 529,825  - ECC cryptography PASSED  17.12s

Delphi XE7-64 pascal
  - ecc_make_key: 1,000 assertions passed  1.55s
  - ecdsa_sign: 1,000 assertions passed  1.57s
  - ecdsa_verify: 1,000 assertions passed  1.86s
  - ecdh_shared_secret: 2,997 assertions passed  3.23s
  Total failed: 0 / 529,825  - ECC cryptography PASSED

Delphi XE7-64 ECC_O2
  - ecc_make_key: 1,000 assertions passed  729.49ms
  - ecdsa_sign: 1,000 assertions passed  740.38ms
  - ecdsa_verify: 1,000 assertions passed  914.80ms
  - ecdh_shared_secret: 2,997 assertions passed  1.55s
  Total failed: 0 / 529,825  - ECC cryptography PASSED

FPC Win64 pascal
  - ecc_make_key: 1,000 assertions passed  1.43s
  - ecdsa_sign: 1,000 assertions passed  1.45s
  - ecdsa_verify: 1,000 assertions passed  1.76s
  - ecdh_shared_secret: 2,997 assertions passed  3.03s
  Total failed: 0 / 529,825  - ECC cryptography PASSED

FPC Linux x86-64 pascal
  - ecc_make_key: 1,000 assertions passed  1.42s
  - ecdsa_sign: 1,000 assertions passed  1.48s
  - ecdsa_verify: 1,000 assertions passed  1.80s
  - ecdh_shared_secret: 2,997 assertions passed  3.11s
  Total failed: 0 / 529,825  - ECC cryptography PASSED
}

const
  NUM_ECC_DIGITS = ECC_BYTES div 8; // =4
  MAX_TRIES = 16;

type // we use UInt64 instead of QWord
  TVLI = array[0..NUM_ECC_DIGITS-1] of UInt64;
  PVLI = ^TVLI;
  TVLI2 = array[0..(2*NUM_ECC_DIGITS)-1] of UInt64;

  UInt128 = record
    m_low: UInt64;
    m_high: UInt64;
  end;
  TEccPoint = record
    x, y: TVLI;
  end;
  PEccPoint = ^TEccPoint;

function _isZero(const VLI: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif}
begin
  result := (VLI[0]=0) and (VLI[1]=0) and (VLI[2]=0) and (VLI[3]=0);
end;

{$ifdef HASUINT64}

const
  Curve_P_32: TVLI = (
    UInt64($FFFFFFFFFFFFFFFF), UInt64($00000000FFFFFFFF),
    UInt64($0000000000000000), UInt64($FFFFFFFF00000001));
  Curve_B_32: TVLI = (
    UInt64($3BCE3C3E27D2604B), UInt64($651D06B0CC53B0F6),
    UInt64($B3EBBD55769886BC), UInt64($5AC635D8AA3A93E7));
  Curve_G_32: TEccPoint = (
    x: (UInt64($F4A13945D898C296), UInt64($77037D812DEB33A0),
        UInt64($F8BCE6E563A440F2), UInt64($6B17D1F2E12C4247));
    y: (UInt64($CBB6406837BF51F5), UInt64($2BCE33576B315ECE),
        UInt64($8EE7EB4A7C0F9E16), UInt64($4FE342E2FE1A7F9B)));
  Curve_N_32: TVLI = (
    UInt64($F3B9CAC2FC632551), UInt64($BCE6FAADA7179E84),
    UInt64($FFFFFFFFFFFFFFFF), UInt64($FFFFFFFF00000000));

  _1: TVLI = (1, 0, 0, 0);
  _3: TVLI = (3, 0, 0, 0);
  _11: TVLI = (UInt64($0101010101010101), UInt64($0101010101010101),
               UInt64($0101010101010101), UInt64($0101010101010101));

procedure _clear(out VLI: TVLI); {$ifdef HASINLINE}inline;{$endif}
begin
  VLI[0] := 0;
  VLI[1] := 0;
  VLI[2] := 0;
  VLI[3] := 0;
end;

function _equals(const Left, Right: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif}
begin
  result := (Left[0]=Right[0]) and (Left[1]=Right[1]) and (Left[2]=Right[2]) and
    (Left[3]=Right[3]);
end;

// counts the number of bits required for VLI
function _numBits(const VLI: TVLI): integer;
var i: integer;
    digit: UInt64;
begin
  if VLI[3]<>0 then
    result := 4 else
  if VLI[2]<>0 then
    result := 3 else
  if VLI[1]<>0 then
    result := 2 else
  if VLI[0]<>0 then
    result := 1 else begin
    result := 0;
    exit;
  end;
  digit := VLI[result-1];
  i := 0;
  while digit > 0 do begin
    inc(i);
    digit := digit shr 1;
  end;
  result := (result-1)*64 + i
end;

// returns sign of Left - Right
function _cmp(const Left, Right: TVLI): integer; {$ifdef HASINLINE}inline;{$endif}
begin
  if Left[3] > Right[3] then
    result := 1 else
  if Left[3] < Right[3] then
    result := -1 else
  if Left[2] > Right[2] then
    result := 1 else
  if Left[2] < Right[2] then
    result := -1 else
  if Left[1] > Right[1] then
    result := 1 else
  if Left[1] < Right[1] then
    result := -1 else
  if Left[0] > Right[0] then
    result := 1 else
  if Left[0] < Right[0] then
    result := -1 else
    result := 0;
end;

// computes Output = Input shl Shift, returning carry
// can modify in place (if Output == Input). 0 < Shift < 64
function _lshift(var Output: TVLI; const Input: TVLI; Shift: integer): UInt64;
var temp: UInt64;
    revShift: integer;
begin
  revShift := 64 - Shift;
  result := Input[0] shr revShift;
  Output[0] := Input[0] shl Shift;
  temp := Input[1];
  Output[1] := (temp shl Shift) or result;
  result := temp shr revShift;
  temp := Input[2];
  Output[2] := (temp shl Shift) or result;
  result := temp shr revShift;
  temp := Input[3];
  Output[3] := (temp shl Shift) or result;
  result := temp shr revShift;
end;

{$ifdef CPU32}
procedure _rshift1(var VLI64: TVLI);
var VLI: TCardinalArray absolute VLI64;
    carry, temp: cardinal;
begin
  carry := VLI[7] shl 31;
  VLI[7] := VLI[7] shr 1;
  temp := VLI[6];
  VLI[6] := (temp shr 1) or carry;
  carry := temp shl 31;
  temp := VLI[5];
  VLI[5] := (temp shr 1) or carry;
  carry := temp shl 31;
  temp := VLI[4];
  VLI[4] := (temp shr 1) or carry;
  carry := temp shl 31;
  temp := VLI[3];
  VLI[3] := (temp shr 1) or carry;
  carry := temp shl 31;
  temp := VLI[2];
  VLI[2] := (temp shr 1) or carry;
  carry := temp shl 31;
  temp := VLI[1];
  VLI[1] := (temp shr 1) or carry;
  carry := temp shl 31;
  temp := VLI[0];
  VLI[0] := (temp shr 1) or carry;
end;
{$else}
procedure _rshift1(var VLI: TVLI);
  {$ifdef HASINLINE}inline;{$endif}
var carry, temp: UInt64;
begin
  carry := VLI[3] shl 63;
  VLI[3] := VLI[3] shr 1;
  temp := VLI[2];
  VLI[2] := (temp shr 1) or carry;
  carry := temp shl 63;
  temp := VLI[1];
  VLI[1] := (temp shr 1) or carry;
  carry := temp shl 63;
  temp := VLI[0];
  VLI[0] := (temp shr 1) or carry;
end;
{$endif}

{$ifdef CPU32}
function _lshift1(var VLI64: TVLI): cardinal;
var VLI: TCardinalArray absolute VLI64;
    temp: cardinal;
begin
  result := VLI[0] shr 31;
  VLI[0] := VLI[0] shl 1;
  temp := VLI[1];
  VLI[1] := (temp shl 1) or result;
  result := temp shr 31;
  temp := VLI[2];
  VLI[2] := (temp shl 1) or result;
  result := temp shr 31;
  temp := VLI[3];
  VLI[3] := (temp shl 1) or result;
  result := temp shr 31;
  temp := VLI[4];
  VLI[4] := (temp shl 1) or result;
  result := temp shr 31;
  temp := VLI[5];
  VLI[5] := (temp shl 1) or result;
  result := temp shr 31;
  temp := VLI[6];
  VLI[6] := (temp shl 1) or result;
  result := temp shr 31;
  temp := VLI[7];
  VLI[7] := (temp shl 1) or result;
  result := temp shr 31;
end;
{$else}
function _lshift1(var VLI: TVLI): UInt64;
  {$ifdef HASINLINE}inline;{$endif}
var temp: UInt64;
begin
  result := VLI[0] shr 63;
  VLI[0] := VLI[0] shl 1;
  temp := VLI[1];
  VLI[1] := (temp shl 1) or result;
  result := temp shr 63;
  temp := VLI[2];
  VLI[2] := (temp shl 1) or result;
  result := temp shr 63;
  temp := VLI[3];
  VLI[3] := (temp shl 1) or result;
  result := temp shr 63;
end;
{$endif}

// computes Output = Left + Right, returning carry. Can modify in place
{$ifdef CPU32}
function _add(var Output64: TVLI; const Left64, Right64: TVLI): PtrUInt;
var Output: TCardinalArray absolute Output64;
    Left: TCardinalArray absolute Left64;
    Right: TCardinalArray absolute Right64;
{$else}
function _add(var Output: TVLI; const Left, Right: TVLI): PtrUInt;
  {$ifdef HASINLINE}inline;{$endif}
{$endif}
var sum: PtrUInt;
begin
  result := 0;
  sum := Left[0] + Right[0];
  if sum <> Left[0] then
    if sum < Left[0] then
      result := 1;
  Output[0] := sum;
  sum := Left[1] + Right[1] + result;
  if sum <> Left[1] then
    if sum < Left[1] then
      result := 1 else
      result := 0;
  Output[1] := sum;
  sum := Left[2] + Right[2] + result;
  if sum <> Left[2] then
    if sum < Left[2] then
      result := 1 else
      result := 0;
  Output[2] := sum;
  sum := Left[3] + Right[3] + result;
  if sum <> Left[3] then
    if sum < Left[3] then
      result := 1 else
      result := 0;
  Output[3] := sum;
  {$ifdef CPU32}
  sum := Left[4] + Right[4] + result;
  if sum <> Left[4] then
    if sum < Left[4] then
      result := 1 else
      result := 0;
  Output[4] := sum;
  sum := Left[5] + Right[5] + result;
  if sum <> Left[5] then
    if sum < Left[5] then
      result := 1 else
      result := 0;
  Output[5] := sum;
  sum := Left[6] + Right[6] + result;
  if sum <> Left[6] then
    if sum < Left[6] then
      result := 1 else
      result := 0;
  Output[6] := sum;
  sum := Left[7] + Right[7] + result;
  if sum <> Left[7] then
    if sum < Left[7] then
      result := 1 else
      result := 0;
  Output[7] := sum;
  {$endif}
end;

// computes Output = Left + Right, returning borrow. Can modify in place.
{$ifdef CPU32}
function _sub(var Output64: TVLI; const Left64, Right64: TVLI): PtrUInt;
var Output: TCardinalArray absolute Output64;
    Left: TCardinalArray absolute Left64;
    Right: TCardinalArray absolute Right64;
{$else}
function _sub(var Output: TVLI; const Left, Right: TVLI): PtrUInt;
  {$ifdef HASINLINE}inline;{$endif}
{$endif}
var diff: PtrUInt;
begin
  result := 0;
  diff := Left[0] - Right[0] - result;
  if diff <> Left[0] then
    if diff > Left[0] then
      result := 1;
  Output[0] := diff;
  diff := Left[1] - Right[1] - result;
  if diff <> Left[1] then
    if diff > Left[1] then
      result := 1 else
      result := 0;
  Output[1] := diff;
  diff := Left[2] - Right[2] - result;
  if diff <> Left[2] then
    if diff > Left[2] then
      result := 1 else
      result := 0;
  Output[2] := diff;
  diff := Left[3] - Right[3] - result;
  if diff <> Left[3] then
    if diff > Left[3] then
      result := 1 else
      result := 0;
  Output[3] := diff;
  {$ifdef CPU32}
  diff := Left[4] - Right[4] - result;
  if diff <> Left[4] then
    if diff > Left[4] then
      result := 1 else
      result := 0;
  Output[4] := diff;
  diff := Left[5] - Right[5] - result;
  if diff <> Left[5] then
    if diff > Left[5] then
      result := 1 else
      result := 0;
  Output[5] := diff;
  diff := Left[6] - Right[6] - result;
  if diff <> Left[6] then
    if diff > Left[6] then
      result := 1 else
      result := 0;
  Output[6] := diff;
  diff := Left[7] - Right[7] - result;
  if diff <> Left[7] then
    if diff > Left[7] then
      result := 1 else
      result := 0;
  Output[7] := diff;
  {$endif}
end;

procedure _mult(out Output: TVLI2; const Left, Right: TVLI);
var i, k, l_min: integer;
    Product, r01: UInt128;
    carry, prev: UInt64;
    l, r: ^UInt64;
begin
  r01.m_low := 0;
  r01.m_high := 0;
  carry := 0;
  // Compute each digit of Output in sequence, maintaining the carries
  for k := 0 to 2 * NUM_ECC_DIGITS - 2 do begin
    if k < NUM_ECC_DIGITS then
      l_min := 0 else
      l_min := (k + 1) - NUM_ECC_DIGITS;
    l := @Left[l_min];
    r := @Right[k-l_min];
    for i := l_min to k do begin
      if i >= NUM_ECC_DIGITS then
        break;
      mul64x64(l^, r^, THash128Rec(Product));
      prev := r01.m_low;
      inc(r01.m_low, Product.m_low);
      inc(r01.m_high, Product.m_high);
      if r01.m_low < prev then
        inc(r01.m_high);
      if r01.m_high < Product.m_high then
        inc(carry);
      inc(l);
      dec(r);
    end;
    Output[k] := r01.m_low;
    r01.m_low := r01.m_high;
    r01.m_high := carry;
    carry := 0;
  end;
  Output[NUM_ECC_DIGITS * 2 - 1] := r01.m_low;
end;

procedure _square(out Output: TVLI2; const Left: TVLI);
var i, k, l_min: integer;
    Product, r01: UInt128;
    carry, prev: UInt64;
begin
  r01.m_low := 0;
  r01.m_high := 0;
  carry := 0;
  for k := 0 to 2 * NUM_ECC_DIGITS - 2 do begin
    if k < NUM_ECC_DIGITS then
      l_min := 0 else
      l_min := (k + 1) - NUM_ECC_DIGITS;
    for i := l_min to k do begin
      if i > k-i then
        break;
      mul64x64(Left[i], Left[k-i], THash128Rec(Product));
      if i < k-i then begin
        inc(carry, Product.m_high shr 63);
        Product.m_high := (Product.m_high shl 1) or (Product.m_low shr 63);
        Product.m_low := Product.m_low shl 1;
      end;
      prev := r01.m_low;
      inc(r01.m_low, Product.m_low);
      inc(r01.m_high, Product.m_high);
      if r01.m_low < prev then
        inc(r01.m_high);
      if r01.m_high < Product.m_high then
        inc(carry);
    end;
    Output[k] := r01.m_low;
    r01.m_low := r01.m_high;
    r01.m_high := carry;
    carry := 0;
  end;
  Output[NUM_ECC_DIGITS*2-1] := r01.m_low;
end;

// computes result = (Left + Right) mod Modulo
// assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod
procedure _modAdd(var Output: TVLI; const Left, Right, Modulo: TVLI); {$ifdef HASINLINE}inline;{$endif}
begin
  if (_add(Output, Left, Right) <> 0) or (_cmp(Output, Modulo) >= 0) then
    // result > Modulo (result = Modulo + Remainder), so subtract Modulo to get remainder
    _sub(Output, Output, Modulo);
end;

// computes result = (Left - Right) mod Modulo.
// assumes that Left < Modulo and Right < Modulo, result != Modulo
procedure _modSub(out Output: TVLI; const Left, Right, Modulo: TVLI); {$ifdef HASINLINE}inline;{$endif}
begin
  if _sub(Output, Left, Right) > 0 then
    // In this case, Output == -diff == (max int) - diff.
    // Since -x mod d == d - x, we can get the correct result from Output + Modulo (with overflow)
    _add(Output, Output, Modulo);
end;

// computes result = Product mod Curve
// from http://www.nsa.gov/ia/_files/nist-routines.pdf
procedure _mmod_fast(out Output: TVLI; var p_product: TVLI2);
var carry: integer;
    l_tmp: TVLI;
begin
  // t
  Output := PVLI(@p_product)^;
  // s1
  l_tmp[0] := 0;
  l_tmp[1] := p_product[5] and $FFFFFFFF00000000;
  l_tmp[2] := p_product[6];
  l_tmp[3] := p_product[7];
  carry := _lshift1(l_tmp);
  inc(carry, _add(Output, Output, l_tmp));
  // s2
  l_tmp[1] := p_product[6] shl 32;
  l_tmp[2] := (p_product[6] shr 32) or (p_product[7] shl 32);
  l_tmp[3] := p_product[7] shr 32;
  inc(carry, _lshift1(l_tmp));
  inc(carry, _add(Output, Output, l_tmp));
  // s3
  l_tmp[0] := p_product[4];
  l_tmp[1] := p_product[5] and $FFFFFFFF;
  l_tmp[2] := 0;
  l_tmp[3] := p_product[7];
  inc(carry, _add(Output, Output, l_tmp));
  // s4
  l_tmp[0] := (p_product[4] shr 32) or (p_product[5] shl 32);
  l_tmp[1] := (p_product[5] shr 32) or (p_product[6] and $FFFFFFFF00000000);
  l_tmp[2] := p_product[7];
  l_tmp[3] := (p_product[6] shr 32) or (p_product[4] shl 32);
  inc(carry, _add(Output, Output, l_tmp));
  // d1
  l_tmp[0] := (p_product[5] shr 32) or (p_product[6] shl 32);
  l_tmp[1] := (p_product[6] shr 32);
  l_tmp[2] := 0;
  l_tmp[3] := (p_product[4] and $FFFFFFFF) or (p_product[5] shl 32);
  dec(carry, _sub(Output, Output, l_tmp));
  // d2
  l_tmp[0] := p_product[6];
  l_tmp[1] := p_product[7];
  l_tmp[2] := 0;
  l_tmp[3] := (p_product[4] shr 32) or (p_product[5] and $FFFFFFFF00000000);
  dec(carry, _sub(Output, Output, l_tmp));
  // d3
  l_tmp[0] := (p_product[6] shr 32) or (p_product[7] shl 32);
  l_tmp[1] := (p_product[7] shr 32) or (p_product[4] shl 32);
  l_tmp[2] := (p_product[4] shr 32) or (p_product[5] shl 32);
  l_tmp[3] := (p_product[6] shl 32);
  dec(carry, _sub(Output, Output, l_tmp));
  // d4
  l_tmp[0] := p_product[7];
  l_tmp[1] := p_product[4] and $FFFFFFFF00000000;
  l_tmp[2] := p_product[5];
  l_tmp[3] := p_product[6] and $FFFFFFFF00000000;
  dec(carry, _sub(Output, Output, l_tmp));
  if carry < 0 then
    repeat
      inc(carry, _add(Output, Output, Curve_P_32));
    until carry >= 0 else
    while (carry <> 0) or (_cmp(Curve_P_32, Output) <> 1) do
      dec(carry, _sub(Output, Output, Curve_P_32));
end;

// computes result = (Left * Right) mod Curve
procedure _modMult_fast(out Output: TVLI; const Left, Right: TVLI); {$ifdef HASINLINE}inline;{$endif}
var Product: TVLI2;
begin
  _mult(Product, Left, Right);
  _mmod_fast(Output, Product);
end;

// computes result = Left^2 mod Curve
procedure _modSquare_fast(out Output: TVLI; const Left: TVLI; const Debug: boolean); {$ifdef HASINLINE}inline;{$endif}
var Product: TVLI2;
begin
  _square(Product, Left);
  _mmod_fast(Output, Product);
end;

// computes result = (1 / p_input) mod Modulo. All VLIs are the same size
// See "From Euclid's GCD to Montgomery Multiplication to the Great Divide"
// https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf
procedure _modInv(out Output: TVLI; const Input, Modulo: TVLI);
var a, b, u, v: TVLI;
    carry: PtrUInt;
    cmp: integer;
begin
  if _isZero(Input) then begin
    _clear(Output);
    exit;
  end;
  a := Input;
  b := Modulo;
  u := _1;
  _clear(v);
  while true do begin
    cmp := _cmp(a, b);
    if cmp = 0 then
      break;
    carry := 0;
    if (byte(a[0]) and 1) = 0 then begin
      _rshift1(a);
      if (byte(u[0]) and 1) = 1 then
        carry := _add(u, u, Modulo);
      _rshift1(u);
      if carry <> 0 then
        THash256(u)[ECC_BYTES-1] := THash256(u)[ECC_BYTES-1] or $80;
    end else
    if (byte(b[0]) and 1) = 0 then begin
      _rshift1(b);
      if (byte(v[0]) and 1) = 1 then
        carry := _add(v, v, Modulo);
      _rshift1(v);
      if carry <> 0 then
        THash256(v)[ECC_BYTES-1] := THash256(v)[ECC_BYTES-1] or $80;
    end else
    if cmp > 0 then begin
      _sub(a, a, b);
      _rshift1(a);
      if _cmp(u, v) < 0 then
        _add(u, u, Modulo);
      _sub(u, u, v);
      if (byte(u[0]) and 1) = 1 then
        carry := _add(u, u, Modulo);
      _rshift1(u);
      if carry <> 0 then
        THash256(u)[ECC_BYTES-1] := THash256(u)[ECC_BYTES-1] or $80;
    end else begin
      _sub(b, b, a);
      _rshift1(b);
      if _cmp(v, u) < 0 then
        _add(v, v, Modulo);
      _sub(v, v, u);
      if (byte(v[0]) and 1) = 1 then
        carry := _add(v, v, Modulo);
      _rshift1(v);
      if carry > 0 then
        THash256(v)[ECC_BYTES-1] := THash256(v)[ECC_BYTES-1] or $80;
    end;
  end;
  Output := u;
end;

// Point multiplication algorithm using Montgomery's ladder with co-Z coordinates.
// From http://eprint.iacr.org/2011/338.pdf

// Double in place
procedure EccPointDoubleJacobian(var X1, Y1, Z1: TVLI);
var carry: UInt64;
    t4, t5: TVLI;
begin
  // t1 = X, t2 = Y, t3 = Z
  if _isZero(Z1) then
    exit;
  _modSquare_fast(t4, Y1, false);  // t4 = y1^2
  _modMult_fast(t5, X1, t4);       // t5 = x1*y1^2 = A
  _modSquare_fast(t4, t4, false);  // t4 = y1^4
  _modMult_fast(Y1, Y1, Z1);       // t2 = y1*z1 = z3
  _modSquare_fast(Z1, Z1, false);  // t3 = z1^2
  _modAdd(X1, X1, Z1, Curve_P_32); // t1 = x1 + z1^2
  _modAdd(Z1, Z1, Z1, Curve_P_32); // t3 = 2*z1^2
  _modSub(Z1, X1, Z1, Curve_P_32); // t3 = x1 - z1^2
  _modMult_fast(X1, X1, Z1);       // t1 = x1^2 - z1^4
  _modAdd(Z1, X1, X1, Curve_P_32); // t3 = 2*(x1^2 - z1^4)
  _modAdd(X1, X1, Z1, Curve_P_32); // t1 = 3*(x1^2 - z1^4)
  if GetBitPtr(@X1, 0) then begin
    carry := _add(X1, X1, Curve_P_32);
    _rshift1(X1);
    X1[NUM_ECC_DIGITS-1] := X1[NUM_ECC_DIGITS-1] or (carry shl 63);
  end else
    _rshift1(X1);
  // t1 = 3/2*(x1^2 - z1^4) = B
  _modSquare_fast(Z1, X1, false);  // t3 = B^2
  _modSub(Z1, Z1, t5, Curve_P_32); // t3 = B^2 - A
  _modSub(Z1, Z1, t5, Curve_P_32); // t3 = B^2 - 2A = x3
  _modSub(t5, t5, Z1, Curve_P_32); // t5 = A - x3
  _modMult_fast(X1, X1, t5);       // t1 = B * (A - x3)
  _modSub(t4, X1, t4, Curve_P_32); // t4 = B * (A - x3) - y1^4 = y3
  X1 := Z1;
  Z1 := Y1;
  Y1 := t4;
end;

// Modify (x1, y1) => (x1 * z^2, y1 * z^3)
procedure _apply_z(var X1, Y1, Z: TVLI);
var t1: TVLI;
begin
  _modSquare_fast(t1, Z, false); // z^2
  _modMult_fast(X1, X1, t1);     // x1 * z^2
  _modMult_fast(t1, t1, Z);      // z^3
  _modMult_fast(Y1, Y1, t1);     // y1 * z^3
end;

// P = (x1, y1) => 2P, (x2, y2) => P'
procedure _XYcZ_initial_double(var X1, Y1, X2, Y2: TVLI; InitialZ: PVLI);
var z: TVLI;
begin
  X2 := X1;
  Y2 := Y1;
  if InitialZ <> nil then
    z := InitialZ^ else
    z := _1;
  _apply_z(X1, Y1, z);
  EccPointDoubleJacobian(X1, Y1, z);
  _apply_z(X2, Y2, z);
end;

// Input P = (x1, y1, Z), Q = (x2, y2, Z)
// Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
// or P => P', Q => P + Q
procedure _XYcZ_add(var X1, Y1, X2, Y2: TVLI);
var t5: TVLI;
begin
  // t1 = X1, t2 = Y1, t3 = X2, t4 = Y2
  _modSub(t5, X2, X1, Curve_P_32); // t5 = x2 - x1
  _modSquare_fast(t5, t5, false);  // t5 = (x2 - x1)^2 = A
  _modMult_fast(X1, X1, t5);       // t1 = x1*A = B
  _modMult_fast(X2, X2, t5);       // t3 = x2*A = C
  _modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y2 - y1
  _modSquare_fast(t5, Y2, false);  // t5 = (y2 - y1)^2 = D
  _modSub(t5, t5, X1, Curve_P_32); // t5 = D - B
  _modSub(t5, t5, X2, Curve_P_32); // t5 = D - B - C = x3
  _modSub(X2, X2, X1, Curve_P_32); // t3 = C - B
  _modMult_fast(Y1, Y1, X2);       // t2 = y1*(C - B)
  _modSub(X2, X1, t5, Curve_P_32); // t3 = B - x3
  _modMult_fast(Y2, Y2, X2);       // t4 = (y2 - y1)*(B - x3)
  _modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y3
  X2 := t5;
end;

// Input P = (x1, y1, Z), Q = (x2, y2, Z)
//  Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
//  or P => P - Q, Q => P + Q
procedure _XYcZ_addC(var X1, Y1, X2, Y2: TVLI);
var t5, t6, t7: TVLI;
begin
  // t1 = X1, t2 = Y1, t3 = X2, t4 = Y2
  _modSub(t5, X2, X1, Curve_P_32); // t5 = x2 - x1
  _modSquare_fast(t5, t5, false);  // t5 = (x2 - x1)^2 = A
  _modMult_fast(X1, X1, t5);       // t1 = x1*A = B
  _modMult_fast(X2, X2, t5);       // t3 = x2*A = C
  _modAdd(t5, Y2, Y1, Curve_P_32); // t4 = y2 + y1
  _modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y2 - y1
  _modSub(t6, X2, X1, Curve_P_32); // t6 = C - B
  _modMult_fast(Y1, Y1, t6);       // t2 = y1 * (C - B)
  _modAdd(t6, X1, X2, Curve_P_32); // t6 = B + C
  _modSquare_fast(X2, Y2, false);  // t3 = (y2 - y1)^2
  _modSub(X2, X2, t6, Curve_P_32); // t3 = x3
  _modSub(t7, X1, X2, Curve_P_32); // t7 = B - x3
  _modMult_fast(Y2, Y2, t7);       // t4 = (y2 - y1)*(B - x3)
  _modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y3
  _modSquare_fast(t7, t5, false);  // t7 = (y2 + y1)^2 = F
  _modSub(t7, t7, t6, Curve_P_32); // t7 = x3'
  _modSub(t6, t7, X1, Curve_P_32); // t6 = x3' - B
  _modMult_fast(t6, t6, t5);       // t6 = (y2 + y1)*(x3' - B)
  _modSub(Y1, t6, Y1, Curve_P_32); // t2 = y3'
  X1 := t7;
end;

procedure EccPointMult(out Output: TEccPoint; const Point: TEccPoint; Scalar: TVLI; InitialZ: PVLI);
var Rx, Ry: array[0..1] of TVLI;
    z: TVLI;
    i, nb: integer;
begin
  // R0 and R1
  Rx[1] := Point.x;
  Ry[1] := Point.y;
  _XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], InitialZ);
  for i := _numBits(Scalar) - 2 downto 1 do begin
    if GetBitPtr(@Scalar, i) then
      nb := 0 else
      nb := 1;
    _XYcZ_addC(Rx[1-nb], Ry[1-nb], Rx[nb], Ry[nb]);
    _XYcZ_add(Rx[nb], Ry[nb], Rx[1-nb], Ry[1-nb]);
  end;
  if GetBitPtr(@Scalar, 0) then
    nb := 0 else
    nb := 1;
  _XYcZ_addC(Rx[1-nb], Ry[1-nb], Rx[nb], Ry[nb]);
  // Find final 1/Z value
  _modSub(z, Rx[1], Rx[0], Curve_P_32);  // X1 - X0
  _modMult_fast(z, z, Ry[1-nb]);         // Yb * (X1 - X0)
  _modMult_fast(z, z, Point.x);          // xP * Yb * (X1 - X0)
  _modInv(z, z, Curve_P_32);             // 1 / (xP * Yb * (X1 - X0))
  _modMult_fast(z, z, Point.y);          // yP / (xP * Yb * (X1 - X0))
  _modMult_fast(z, z, Rx[1-nb]);         // Xb * yP / (xP * Yb * (X1 - X0))
  // End 1/Z calculation
  _XYcZ_add(Rx[nb], Ry[nb], Rx[1-nb], Ry[1-nb]);
  _apply_z(Rx[0], Ry[0], z);
  Output.x := Rx[0];
  Output.y := Ry[0];
end;

procedure _bswap256(dest, source: PQWordArray);
begin
  dest[0] := bswap64(source[3]);
  dest[1] := bswap64(source[2]);
  dest[2] := bswap64(source[1]);
  dest[3] := bswap64(source[0]);
end;

// Compute a = sqrt(a) (mod curve_p)
procedure ModSqrt(var a: TVLI);
var i: integer;
    p1, result: TVLI;
begin
  result := _1;
  // Since curve_p == 3 (mod 4) for all supported curves, we can compute
  // sqrt(a) = a^((curve_p + 1) / 4) (mod curve_p)
  _add(p1, Curve_P_32, _1); // p1 = curve_p + 1
  for i := _numBits(p1) - 1 downto 2 do begin
    _modSquare_fast(result, result, false);
    if GetBitPtr(@p1, i) then
      _modMult_fast(result, result, a);
  end;
  a := result;
end;

procedure EccPointDecompress(out Point: TEccPoint; Compressed: PByteArray);
begin
  _bswap256(@Point.x, @Compressed[1]);
  _modSquare_fast(Point.y, Point.x, false);           // y = x^2
  _modSub(Point.y, Point.y, _3, Curve_P_32);          // y = x^2 - 3
  _modMult_fast(Point.y, Point.y, Point.x);           // y = x^3 - 3x
  _modAdd(Point.y, Point.y, Curve_B_32, Curve_P_32);  // y = x^3 - 3x + b
  ModSqrt(Point.y);
  if (Point.y[0] and $01) <> (Compressed[0] and $01) then
    _sub(Point.y, Curve_P_32, Point.y);
end;

function ecc_make_key_pas(out PublicKey: TECCPublicKey; out PrivateKey: TECCPrivateKey): boolean;
var PrivateK: TVLI;
    PublicPoint: TEccPoint;
    tries: integer;
begin
  result := false;
  tries := 0;
  repeat
    inc(tries);
    TAESPRNG.Fill(THash256(PrivateK));
    if tries >= MAX_TRIES then
      exit;
    if _isZero(PrivateK) or _equals(PrivateK, _1) or _equals(PrivateK, _11) then
      continue;
    // Make sure the private key is in the range [1, n-1]
    // For the supported curves, n is always large enough that we only need
    // to subtract once at most
    if _cmp(Curve_N_32, PrivateK) <> 1 then
      _sub(PrivateK, PrivateK, Curve_N_32);
    EccPointMult(PublicPoint, Curve_G_32, PrivateK, nil);
  until not (_isZero(PublicPoint.x) and _isZero(PublicPoint.y));
  _bswap256(@PrivateKey, @PrivateK);
  _bswap256(@PublicKey[1], @PublicPoint.x);
  PublicKey[0] := 2 + (PublicPoint.y[0] and $01);
  result := true;
  _clear(PrivateK); // erase sensitive information from stack
  _clear(PublicPoint.x);
  _clear(PublicPoint.y);
end;

function ecdh_shared_secret_pas(const PublicPoint: TECCPublicKeyUncompressed;
  const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean;
var
    PrivateK: TVLI;
    Product: TEccPoint;
    RandomKey: TVLI;
begin
  TAESPRNG.Fill(THash256(RandomKey));
  _bswap256(@PrivateK, @PrivateKey);
  EccPointMult(Product, TEccPoint(PublicPoint), PrivateK, @RandomKey);
  _bswap256(@Secret, @Product.x);
  result := not (_isZero(Product.x) and _isZero(Product.y));
  _clear(Product.x); // erase sensitive information from stack
  _clear(Product.y);
  _clear(PrivateK);
  _clear(RandomKey);
end;

procedure ecc_uncompress_key_pas(const Compressed: TECCPublicKey;
  out Uncompressed: TECCPublicKeyUncompressed);
begin
  EccPointDecompress(TEccPoint(Uncompressed), @Compressed);
end;

function ecdh_shared_secret_pas(const PublicKey: TECCPublicKey;
  const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean;
var PublicPoint: TECCPublicKeyUncompressed;
begin
  EccPointDecompress(TEccPoint(PublicPoint), @PublicKey);
  result := ecdh_shared_secret_pas(PublicPoint, PrivateKey, Secret);
end;

// computes result = (Left * Right) mod Modulo
procedure _modMult(out Output: TVLI; const Left, Right, Modulo: TVLI);
var carry: UInt64;
    cmp: integer;
    ModMultiple, Product: TVLI2;
    ModMultipleVLI_Lo, ModMultipleVLI_Hi, ProductVLI_Lo, ProductVLI_Hi: PVLI;
    DigitShift, BitShift, ProductBits, ModBits: integer;
    VLI: PVLI;
begin
  ModBits := _numBits(Modulo);
  _mult(Product, Left, Right);
  ProductVLI_Hi := @Product[NUM_ECC_DIGITS];
  ProductVLI_Lo := @Product;
  ProductBits := _numBits(ProductVLI_Hi^);
  if ProductBits > 0 then
    ProductBits := ProductBits + NUM_ECC_DIGITS*64 else
    ProductBits := _numBits(ProductVLI_Lo^);
  if ProductBits < ModBits then begin // l_product < p_mod
    Output := PVLI(@Product)^;
    exit;
  end;
  // Shift p_mod by (LeftBits - ModBits). This multiplies p_mod by the largest
  // power of two possible while still resulting in a number less than p_left
  ModMultipleVLI_Lo := @ModMultiple;
  ModMultipleVLI_Hi := @ModMultiple[NUM_ECC_DIGITS];
  _clear(ModMultipleVLI_Lo^);
  _clear(ModMultipleVLI_Hi^);
  DigitShift := (ProductBits-ModBits) shr 6;
  BitShift := (ProductBits-ModBits) and 63;
  if BitShift > 0 then begin
    VLI := @ModMultiple[DigitShift];
    ModMultiple[DigitShift+NUM_ECC_DIGITS] := _lshift(VLI^, Modulo, BitShift)
  end else begin
    VLI := @ModMultiple[DigitShift];
    VLI^ := Modulo;
  end;
  // Subtract all multiples of Modulo to get the remainder
  while (ProductBits > NUM_ECC_DIGITS*64) or (_cmp(ModMultipleVLI_Lo^, Modulo) >= 0) do begin
    cmp := _cmp(ModMultipleVLI_Hi^, ProductVLI_Hi^);
    if (cmp < 0) or ((cmp = 0) and (_cmp(ModMultipleVLI_Lo^, ProductVLI_Lo^) <= 0)) then begin
      if _sub(ProductVLI_Lo^, ProductVLI_Lo^, ModMultipleVLI_Lo^) > 0 then
        _sub(ProductVLI_Hi^, ProductVLI_Hi^, _1); // borrow
      _sub(ProductVLI_Hi^, ProductVLI_Hi^, ModMultipleVLI_Hi^);
    end;
    carry := (ModMultiple[NUM_ECC_DIGITS] and $01) shl 63;
    _rshift1(ModMultipleVLI_Hi^);
    _rshift1(ModMultipleVLI_Lo^);
    if carry > 0 then
      ModMultiple[NUM_ECC_DIGITS-1] := ModMultiple[NUM_ECC_DIGITS-1] or carry;
    dec(ProductBits);
  end;
  Output := PVLI(@Product)^;
end;

function ecdsa_sign_pas(const PrivateKey: TECCPrivateKey; const Hash: TEccHash;
  out Signature: TECCSignature): boolean;
var k, Temp, S: TVLI;
    P: TEccPoint;
    Tries: integer;
begin
  result := false;
  Tries := 0;
  repeat
    inc(Tries);
    TAESPRNG.Fill(THash256(k));
    if Tries >= MAX_TRIES then
      exit;
    if _isZero(k) or _equals(k, _1) or _equals(k, _11) then
      continue;
    if _cmp(Curve_N_32, k) <> 1 then
      _sub(k, k, Curve_N_32);
    // Temp = k * G
    EccPointMult(P, Curve_G_32, k, nil);
    // r = x1 (mod n)
    if _cmp(Curve_N_32, P.x) <> 1 then
      _sub(P.x, P.x, Curve_N_32);
  until not _isZero(p.x);
  _bswap256(@Signature, @P.x);
  _bswap256(@Temp, @PrivateKey);
  _modMult(S, P.x, Temp, Curve_N_32); // s = r*d
  _bswap256(@Temp, @Hash);
  _modAdd(S, Temp, S, Curve_N_32);    // s = e + r*d
  _modInv(k, k, Curve_N_32);          // k = 1 / k
  _modMult(S, S, k, Curve_N_32);      // s = (e + r*d) / k
  _bswap256(@Signature[ECC_BYTES], @S);
  result := true;
end;

function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed;
  const Hash: TECCHash; const Signature: TECCSignature): boolean;
var i, Index, NumBits: integer;
    PublicPoint: TEccPoint absolute PublicKey;
    SumPoint: TEccPoint;
    Point: PEccPoint;
    Points: array[0..3] of PEccPoint;
    rx, ry, tx, ty, tz, l_r, l_s, u1, u2, z: TVLI;
begin
  result := false;
  _bswap256(@l_r, @Signature);
  _bswap256(@l_s, @Signature[ECC_BYTES]);
  if _isZero(l_r) or _isZero(l_s) or
     (_cmp(Curve_N_32, l_r) <> 1) or (_cmp(Curve_N_32, l_s) <> 1) then
    exit; // r, s must be <> 0 and < n
  // calculate u1 and u2
  _modInv(z, l_s, Curve_N_32);      // Z = s^-1
  _bswap256(@u1, @Hash);
  _modMult(u1, u1, z, Curve_N_32);  // u1 = e/s
  _modMult(u2, l_r, z, Curve_N_32); // u2 = r/s
  // calculate l_sum = G + Q
  SumPoint.x := PublicPoint.x;
  SumPoint.y := PublicPoint.y;
  tx := Curve_G_32.x;
  ty := Curve_G_32.y;
  _modSub(z, SumPoint.x, tx, Curve_P_32); // Z = x2 - x1
  _XYcZ_add(tx, ty, SumPoint.x, SumPoint.y);
  _modInv(z, z, Curve_P_32);              // Z = 1/Z
  _apply_z(SumPoint.x, SumPoint.y, z);
  // use Shamir's trick to calculate u1*G + u2*Q
  Points[0] := nil;
  Points[1] := @Curve_G_32;
  Points[2] := @PublicPoint;
  Points[3] := @SumPoint;
  NumBits := _numBits(u1);
  Index := _numBits(u2);
  if Index > NumBits then
    NumBits := Index;
  if GetBitPtr(@u1, NumBits-1) then
    Index := 1 else
    Index := 0;
  if GetBitPtr(@u2, NumBits-1) then
    inc(Index, 2);
  Point := Points[Index];
  rx := Point.x;
  ry := Point.y;
  z := _1;
  for i := NumBits - 2 downto 0 do begin
    EccPointDoubleJacobian(rx, ry, z);
    if GetBitPtr(@u1, i) then
      Index := 1 else
      Index := 0;
    if GetBitPtr(@u2, i) then
      inc(Index, 2);
    Point := Points[Index];
    if Point <> nil then begin
      tx := Point.x;
      ty := Point.y;
      _apply_z(tx, ty, z);
      _modSub(tz, rx, tx, Curve_P_32); // Z = x2 - x1
      _XYcZ_add(tx, ty, rx, ry);
      _modMult_fast(z, z, tz);
    end;
  end;
  _modInv(z, z, Curve_P_32); // Z = 1/Z
  _apply_z(rx, ry, z);
  // v = x1 (mod n)
  if _cmp(Curve_N_32, rx) <> 1 then
    _sub(rx, rx, Curve_N_32);
  result := IsEqual(THash256(rx), THash256(l_r)); // Accept only if v == r
end;

function ecdsa_verify_pas(const PublicKey: TECCPublicKey; const Hash: TEccHash;
  const Signature: TECCSignature): boolean;
var PublicPoint: TECCPublicKeyUncompressed;
begin
  EccPointDecompress(TEccPoint(PublicPoint), @PublicKey);
  result := ecdsa_verify_pas(PublicPoint, Hash, Signature);
end;

{$else}

function ecc_make_key_pas(out PublicKey: TECCPublicKey; out PrivateKey: TECCPrivateKey): boolean;
begin
  result := false; // we need proper UInt64 support at compiler level
end;

function ecdh_shared_secret_pas(const PublicKey: TECCPublicKey;
  const PrivateKey: TECCPrivateKey; out Secret: TECCSecretKey): boolean;
begin
  result := false; // we need proper UInt64 support at compiler level
end;

function ecdh_shared_secret_pas(const PublicPoint: TECCPublicKeyUncompressed;
  const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean;
begin
  result := false; // we need proper UInt64 support at compiler level
end;

procedure ecc_uncompress_key_pas(const Compressed: TECCPublicKey;
  out Uncompressed: TECCPublicKeyUncompressed);
begin
  FillZero(THash512(Uncompressed));
end;

function ecdsa_sign_pas(const PrivateKey: TECCPrivateKey; const Hash: TECCHash;
  out Signature: TECCSignature): boolean;
begin
  result := false; // we need proper UInt64 support at compiler level
end;

function ecdsa_verify_pas(const PublicKey: TECCPublicKey; const Hash: TECCHash;
  const Signature: TECCSignature): boolean;
begin
  result := false; // we need proper UInt64 support at compiler level
end;

function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed;
  const Hash: TEccHash; const Signature: TECCSignature): boolean;
begin
  result := false; // we need proper UInt64 support at compiler level
end;

{$endif HASUINT64}


{ *********** middle-level certificate-based public-key cryptography *********** }

procedure FillZero(out Priv: TECCPrivateKey); overload;
begin
  PInt64Array(@Priv)^[0] := 0;
  PInt64Array(@Priv)^[1] := 0;
  PInt64Array(@Priv)^[2] := 0;
  PInt64Array(@Priv)^[3] := 0;
end;

const
  // Mon, 01 Aug 2016 encoded as COM/TDateTime value
  ECC_DELTA = 42583;

function NowECCDate: TECCDate;
begin
  result := Trunc(NowUTC) - ECC_DELTA;
end;

function ECCDate(const DateTime: TDateTime): TECCDate;
var now: integer;
begin
  if DateTime=0 then
    result := 0 else begin
    now := Trunc(DateTime) - ECC_DELTA;
    if cardinal(now)>high(TECCDate) then
      result := 0 else
      result := now;
  end;
end;

function ECCToDateTime(ECCDate: TECCDate): TDateTime;
begin
  if ECCDate=0 then
    result := 0 else
    result := ECCDate + ECC_DELTA;
end;

function ECCText(ECCDate: TECCDate; Expanded: boolean): RawUTF8;
begin
  if ECCDate=0 then
    result := '' else
    result := DateToIso8601(ECCDate + ECC_DELTA, Expanded);
end;

function ECCText(const Issuer: TECCCertificateIssuer): RawUTF8;
var tmp: array[0..1] of TECCCertificateIssuer;
begin
  if IsZero(Issuer) then
    result := '' else begin
    tmp[0] := Issuer;
    tmp[1][0] := 0; // add a trailing #0 as expected for trailing bits
    result := BaudotToAscii(@tmp,sizeof(Issuer));
    if result='' then
      result := SynCommons.BinToHex(@Issuer,sizeof(Issuer));
  end;
end;

function ECCIssuer(const Text: RawUTF8; out Issuer: TECCCertificateIssuer): boolean;
var baudot: RawByteString;
    len: integer;
begin
  FillZero(THash128(Issuer));
  baudot := AsciiToBaudot(Text);
  len := length(baudot);
  result := len>sizeof(Issuer);
  if result then // truncated
    len := sizeof(Issuer);
  MoveFast(pointer(baudot)^,Issuer,len);
end;

function ECCText(const ID: TECCCertificateID): RawUTF8;
begin
  if IsZero(ID) then
    result := '' else
    result := AESBlockToString(TAESBlock(ID));
end;

function ECCID(const Text: RawUTF8; out ID: TECCCertificateID): boolean;
begin
  if length(Text)<>sizeof(ID)*2 then
    result := false else
    result := SynCommons.HexToBin(pointer(Text),@ID,sizeof(ID));
end;

function ECCCheck(const content: TECCCertificateContent): boolean;
begin
  with content.Signed do
    if (IssueDate=0) or (IssueDate=65535) or IsZero(Serial) or IsZero(Issuer) or
       IsZero(AuthoritySerial) or IsZero(AuthorityIssuer) or
       IsZero(@PublicKey,sizeof(PublicKey)) or
       IsZero(@content.Signature,sizeof(content.Signature)) then
      result := false else
      result := (content.Version in [1]) and
                (fnv32(0,@content,sizeof(content)-4)=content.CRC);
end;

function ECCCheckDate(const content: TECCCertificateContent): boolean;
var now: TECCDate;
begin
  now := NowECCDate;
  with content.Signed do
    result := (IssueDate<=now) and
              ((ValidityStart=0) or (ValidityStart<=now)) and
              ((ValidityEnd=0) or (ValidityEnd>=now));
end;

function IsEqual(const issuer1,issuer2: TECCCertificateIssuer): boolean; overload;
var a: TPtrIntArray absolute issuer1;
    b: TPtrIntArray absolute issuer2;
begin
  result := (a[0]=b[0]) and (a[1]=b[1])
    {$ifndef CPU64} and (a[2]=b[2]) and (a[3]=b[3]){$endif};
end;

function IsEqual(const id1,id2: TECCCertificateID): boolean; overload;
var a: TPtrIntArray absolute id1;
    b: TPtrIntArray absolute id2;
begin
  result := (a[0]=b[0]) and (a[1]=b[1])
    {$ifndef CPU64} and (a[2]=b[2]) and (a[3]=b[3]){$endif};
end;

function IsZero(const issuer: TECCCertificateIssuer): boolean;
var a: TPtrIntArray absolute issuer;
begin
  result := (a[0]=0) and (a[1]=0)
     {$ifndef CPU64} and (a[2]=0) and (a[3]=0){$endif};
end;

function IsZero(const id: TECCCertificateID): boolean;
var a: TPtrIntArray absolute id;
begin
  result := (a[0]=0) and (a[1]=0)
     {$ifndef CPU64} and (a[2]=0) and (a[3]=0){$endif};
end;

function ECCSelfSigned(const content: TECCCertificateContent): boolean;
begin
  with content.Signed do
    result := IsEqual(AuthoritySerial,Serial) and not IsZero(Serial) and
              IsEqual(AuthorityIssuer,Issuer);
end;

function ECCCheck(const content: TECCSignatureCertifiedContent): boolean;
begin
  result := (content.Version in [1]) and (content.Date<>0) and
    not IsZero(content.AuthoritySerial) and not IsZero(content.AuthorityIssuer) and
    not IsZero(@content.Signature,sizeof(content.Signature));
end;

function ECCSign(const base64: RawUTF8; out content: TECCSignatureCertifiedContent): boolean;
begin
  result := Base64ToBin(pointer(base64),@content,length(base64),sizeof(content),false);
end;

const
  DER_SEQUENCE = $30;
  DER_INTEGER  = #$02;

function DerAppend(P: PAnsiChar; vli: PByteArray): PAnsiChar;
var pos, prefix: PtrUInt;
begin
  pos := 0;
  while vli[pos] = 0 do inc(pos); // ignore trailing zeros
  prefix := vli[pos] shr 7; // two's complement?
  P[0] := DER_INTEGER;
  P[1] := AnsiChar(ECC_BYTES - pos + prefix);
  P[2] := #$00; // prepend 0 for negative number (if prefix=1)
  inc(P, 2 + prefix);
  MoveSmall(@vli[pos], P, ECC_BYTES - pos);
  result := P + ECC_BYTES - pos;
end;

function EccSignToDer(const sign: TEccSignature; out der: TEccSignatureDer): integer;
begin
  if _isZero(PVLI(@sign[0])^) or _isZero(PVLI(@sign[ECC_BYTES])^) then
    result := 0
  else begin
    result := DerAppend(DerAppend(@der[2],@sign[0]),@sign[ECC_BYTES])-PAnsiChar(@der);
    der[0] := DER_SEQUENCE;
    der[1] := result - 2;
  end;
end;

function ECCText(const sign: TECCSignatureCertifiedContent): RawUTF8; overload;
begin
  if ECCCheck(sign) then
    result := BinToBase64(@sign,sizeof(sign)) else
    result := '';
end;

function ECCText(const sign: TECCSignature): RawUTF8; overload;
begin
  if IsZero(@sign,sizeof(sign)) then
    result := '' else
    result := BinToBase64(@sign,sizeof(sign));
end;

function ECCVerify(const sign: TECCSignatureCertifiedContent;
  const hash: THash256; const auth: TECCCertificateContent): TECCValidity;
begin
  if IsZero(hash) then
    result := ecvBadParameter else
  if not ECCCheck(sign) then
    result := ecvCorrupted else
  if sign.Date>NowECCDate then
    result := ecvInvalidDate else
  if not ECCCheck(auth) then
    result := ecvUnknownAuthority else
  if not ECCCheckDate(auth) then
    result := ecvDeprecatedAuthority else
  if not ecdsa_verify(auth.Signed.PublicKey,hash,sign.Signature) then
    result := ecvInvalidSignature else
  if ECCSelfSigned(auth) then
    result := ecvValidSelfSigned else
    result := ecvValidSigned;
end;

const
  ECIES_MAGIC: array[0..1] of array[0..15] of AnsiChar =
    ('SynEccEncrypted'#26, 'SynEccEncrypt01'#26);
  ECIES_NOSYNLZ: array[ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ..
    ecaPBKDF2_HMAC_SHA256_AES128_CTR_SYNLZ] of TECIESAlgo = (
      ecaPBKDF2_HMAC_SHA256_AES256_CFB, ecaPBKDF2_HMAC_SHA256_AES256_CBC,
      ecaPBKDF2_HMAC_SHA256_AES256_OFB, ecaPBKDF2_HMAC_SHA256_AES256_CTR,
      ecaPBKDF2_HMAC_SHA256_AES128_CFB, ecaPBKDF2_HMAC_SHA256_AES128_CBC,
      ecaPBKDF2_HMAC_SHA256_AES128_OFB, ecaPBKDF2_HMAC_SHA256_AES128_CTR);
  ECIES_AES: array[ecaPBKDF2_HMAC_SHA256_AES256_CFB..
    ecaPBKDF2_HMAC_SHA256_AES128_CTR] of TAESAbstractClass = (
      TAESCFB, TAESCBC, TAESOFB, TAESCTR, TAESCFB, TAESCBC, TAESOFB, TAESCTR,
      TAESCFB, TAESCBC, TAESOFB, TAESCTR, TAESCFB, TAESCBC, TAESOFB, TAESCTR);
  ECIES_AESSIZE: array[ecaPBKDF2_HMAC_SHA256_AES256_CFB..
    ecaPBKDF2_HMAC_SHA256_AES128_CTR] of integer = (
      256, 256, 256, 256, 256, 256, 256, 256,
      128, 128, 128, 128, 128, 128, 128, 128);
type
  TECIESFeatures = set of (efMetaData);

function ECIESLevel(const head: TECIESHeader): integer;
  // inline; defeats Delphi optimizer for IsEqual()
begin
  for result := 0 to high(ECIES_MAGIC) do
    if IsEqual(head.magic,THash128(ECIES_MAGIC[result])) then
      exit;
  result := -1;
end;

function ECIESFeatures(const head: TECIESHeader): TECIESFeatures;
var level: integer;
begin
  byte(result) := 0;
  level := ECIESLevel(head);
  if level>0 then
    include(result,efMetaData);
end;

function ECIESHeader(const head: TECIESHeader): boolean;
begin
  result := (ECIESLevel(head)>=0) and (head.Algo in [Low(ECIES_AES)..High(ECIES_AES)]) and
    (head.crc=crc32c(PCardinal(@head.hmac)^,@head,sizeof(head)-sizeof(head.crc)));
end;

function ECIESHeader(const encrypted: RawByteString; out head: TECIESHeader): boolean;
begin
  result := (length(encrypted)>sizeof(head)) and ECIESHeader(PECIESHeader(encrypted)^);
  if result then
    head := PECIESHeader(encrypted)^;
end;

function ECIESHeaderFile(const encryptedfile: TFileName; out head: TECIESHeader;
  const rawencryptedfile: TFileName): boolean;
var F: THandle;
    len: integer;
    tmp: RawByteString;
begin
  result := false;
  if encryptedfile='' then
    exit;
  F := FileOpen(encryptedfile,fmOpenRead or fmShareDenyNone);
  if PtrInt(F)<0 then
    exit;
  if FileRead(F,head,sizeof(head))=sizeof(head) then
    result := ECIESHeader(head);
  if result and (rawencryptedfile<>'') then begin
    len := GetFileSize(F,nil)-sizeof(head);
    SetLength(tmp,len);
    if FileRead(F,pointer(tmp)^,len)<>len then
      result := false else
      result := FileFromString(tmp,rawencryptedfile);
  end;
  FileClose(F);
end;

function ECIESHeaderText(const head: TECIESHeader): RawUTF8;
{$ifdef NOVARIANTS}
var s: RawUTF8;
begin
  s := ECCText(head.sign);
{$else}
var s: variant;
    sign: TECCSignatureCertified;
begin
  sign := TECCSignatureCertified.CreateFrom(head.sign,true);
  try
    if sign.Check then begin
      s := sign.ToVariant;
      TDocVariantData(s).AddValueFromText('ECDSA',ECCText(head.sign.Signature));
    end;
  finally
    sign.Free;
  end;
{$endif}
  with head do
    result := JSONEncode(['Date',ECCText(date), 'Size',size,
      'Recipient',ECCText(rec), 'RecipientSerial',ECCText(recid),
      'FileTime',DateTimeToIso8601Text(UnixTimeToDateTime(unixts)),
      'Algorithm',ToText(algo)^,
      'RandomPublicKey',SynCommons.BinToHex(@rndpub,sizeof(rndpub)),
      'HMAC',SHA256DigestToString(hmac), 'Signature',s,
      'Meta',(efMetaData in ECIESFeatures(head))]);
end;

function ECIESHeaderText(const encryptedfile,rawencryptedfile: TFileName): RawUTF8; overload;
var h: TECIESHeader;
begin
  if ECIESHeaderFile(encryptedfile,h,rawencryptedfile) then
    result := ECIESHeaderText(h) else
    result := '';
end;


{ *********** high-level certificate-based public-key cryptography *********** }

function ToText(val: TECCValidity): PShortString;
begin
  result := GetEnumName(TypeInfo(TECCValidity),ord(val));
end;

function ToText(res: TECCDecrypt): PShortString;
begin
  result := GetEnumName(TypeInfo(TECCDecrypt),ord(res));
end;

function ToText(algo: TECIESAlgo): PShortString;
begin
  result := GetEnumName(TypeInfo(TECIESAlgo),ord(algo));
end;

function ToText(algo: TECDHEAuth): PShortString;
begin
  result := GetEnumName(TypeInfo(TECDHEAuth),ord(algo));
end;

function ToText(algo: TECDHEKDF): PShortString;
begin
  result := GetEnumName(TypeInfo(TECDHEKDF),ord(algo));
end;

function ToText(algo: TECDHEEF): PShortString;
begin
  result := GetEnumName(TypeInfo(TECDHEEF),ord(algo));
end;

function ToText(algo: TECDHEMAC): PShortString;
begin
  result := GetEnumName(TypeInfo(TECDHEMAC),ord(algo));
end;

var
  _ECCKeyFileFolder: TFileName;

function ECCKeyFileFolder: TFileName;
begin
  if _ECCKeyFileFolder='' then begin
    _ECCKeyFileFolder := GetSystemPath(spUserData)+
      {$ifdef MSWINDOWS}'Synopse\Keys\'{$else}'.synopse/keys/'{$endif};
    if not DirectoryExists(_ECCKeyFileFolder) then
      CreateDir(_ECCKeyFileFolder); // always create this folder
  end;
  result := _ECCKeyFileFolder;
end;

function ECCKeyFileFind(var TruncatedFileName: TFileName; privkey: boolean): boolean;
var match: TFindFilesDynArray;
    ext,mask: TFileName;
begin
  match := nil; // to please Kylix
  if privkey then
    ext := ECCCERTIFICATESECRET_FILEEXT else
    ext := ECCCERTIFICATEPUBLIC_FILEEXT;
  result := true;
  if FileExists(TruncatedFileName) then
    exit;
  if FileExists(TruncatedFileName+ext) then begin
    TruncatedFileName := TruncatedFileName+ext;
    exit;
  end;
  mask := ExtractFileName(TruncatedFileName)+'*'+ext;
  match := FindFiles(ExtractFilePath(TruncatedFileName),mask);
  if length(match)<>1 then
    match := FindFiles(ECCKeyFileFolder,mask);
  if length(match)<>1 then
    result := false else
    TruncatedFileName := match[0].Name;
end;

function ECIESKeyFileFind(const encrypted: RawByteString; out keyfile: TFileName;
  privkey: boolean): boolean;
var head: TECIESHeader;
begin
  result := ECIESHeader(encrypted,head);
  if result then begin
    keyfile := UTF8ToString(ECCText(head.recid));
    result := ECCKeyFileFind(keyfile,privkey);
  end;
end;


{ TECCCertificate }

constructor TECCCertificate.Create;
begin
  inherited Create;
  fContent.Version := 1;
end;

constructor TECCCertificate.CreateFrom(const binary: TECCCertificateContent);
begin
  Create;
  fContent := binary;
  if not ECCCheck(fContent) then
    raise EECCException.CreateUTF8('Invalid %.CreateFrom',[self]);
end;

constructor TECCCertificate.CreateFromBase64(const base64: RawUTF8);
begin
  Create;
  if not FromBase64(base64) then
    raise EECCException.CreateUTF8('Invalid %.CreateFromBase64',[self]);
end;

constructor TECCCertificate.CreateFromAuth(const AuthPubKey: TFileName;
  const AuthBase64, AuthSerial: RawUTF8);
begin
  Create;
  if not FromAuth(AuthPubKey,AuthBase64,AuthSerial) then
    raise EECCException.CreateUTF8('Invalid %.CreateFromAuth',[self]);
end;

function TECCCertificate.GetAuthorityIssuer: RawUTF8;
begin
  result := ECCText(fContent.Signed.AuthorityIssuer);
end;

function TECCCertificate.GetAuthoritySerial: RawUTF8;
begin
  result := ECCText(fContent.Signed.AuthoritySerial);
end;

function TECCCertificate.GetIssueDate: RawUTF8;
begin
  result := ECCText(fContent.Signed.IssueDate);
end;

function TECCCertificate.GetIssuer: RawUTF8;
begin
  result := ECCText(fContent.Signed.Issuer);
end;

function TECCCertificate.GetSerial: RawUTF8;
begin
  result := ECCText(fContent.Signed.Serial);
end;

function TECCCertificate.GetValidityEnd: RawUTF8;
begin
  result := ECCText(fContent.Signed.ValidityEnd);
end;

function TECCCertificate.GetValidityStart: RawUTF8;
begin
  result := ECCText(fContent.Signed.ValidityStart);
end;

function TECCCertificate.GetIsSelfSigned: boolean;
begin
  result := (self<>nil) and ECCSelfSigned(fContent);
end;

function TECCCertificate.CheckCRC: boolean;
begin
  result := (self<>nil) and ECCCheck(fContent);
end;

function TECCCertificate.FromBase64(const base64: RawUTF8): boolean;
var st: TRawByteStringStream;
begin
  if base64='' then
    result := false else begin
    st := TRawByteStringStream.Create(Base64ToBinSafe(base64));
    try
      result := LoadFromStream(st) and ECCCheck(fContent);
    finally
      st.Free;
    end;
  end;
end;

function TECCCertificate.FromFile(const filename: TFileName): boolean;
var json: RawUTF8;
    fn: TFileName;
begin
  if ExtractFileExt(filename)='' then
    fn := filename+ECCCERTIFICATEPUBLIC_FILEEXT else
    fn := filename;
  json := StringFromFile(fn);
  if json='' then
    result := false else
    result := FromBase64(JSONDecode(json,'Base64',nil,true));
end;

function TECCCertificate.FromAuth(const AuthPubKey: TFileName;
  const AuthBase64, AuthSerial: RawUTF8): boolean;
var authfilename: TFileName;
begin
  result := true;
  if FromFile(AuthPubKey) or FromBase64(AuthBase64) then
    exit;
  if AuthSerial<>'' then begin
    authfilename := UTF8ToString(AuthSerial);
    if ECCKeyFileFind(authfilename,false) and FromFile(authfilename) then
      exit;
  end;
  result := false;
end;

procedure TECCCertificate.SetBase64(const base64: RawUTF8);
begin
  FromBase64(base64);
end;

function TECCCertificate.ToBase64: RawUTF8;
var st: TRawByteStringStream;
begin
  st := TRawByteStringStream.Create;
  try
    if SaveToStream(st) then
      result := BinToBase64(st.DataString);
  finally
    st.Free;
  end;
end;

function TECCCertificate.PublicToBase64: RawUTF8;
var sav: boolean;
begin
  sav := fStoreOnlyPublicKey;
  fStoreOnlyPublicKey := true;
  result := ToBase64;
  fStoreOnlyPublicKey := sav;
end;

function TECCCertificate.LoadFromStream(Stream: TStream): boolean;
begin
  result := (Stream.Read(fContent,sizeof(fContent))=sizeof(fContent)) and
    InternalLoad(ReadStringFromStream(Stream,524288));
end;

function TECCCertificate.SaveToStream(Stream: TStream): boolean;
begin
  result := CheckCRC and
    (Stream.Write(fContent,sizeof(fContent))=sizeof(fContent)) and
    WriteStringToStream(Stream,InternalSave);
end;

function TECCCertificate.InternalLoad(const data: RawByteString): boolean;
begin
  result := true;
end;

function TECCCertificate.InternalSave: RawByteString;
begin
  result := '';
end;

{$ifdef ISDELPHI20062007}
  {$WARNINGS OFF} // circument Delphi 2007 false positive warning
{$endif}

function TECCCertificate.Encrypt(const Plain: RawByteString;
  Signature: TECCSignatureCertified; FileDateTime: TDateTime;
  const KDFSalt: RawUTF8; KDFRounds: integer;
  const MACSalt: RawUTF8; MACRounds: integer; Algo: TECIESAlgo): RawByteString;
var rndpriv: TECCPrivateKey;
    head: TECIESHeader;
    secret, dec, enc, content: RawByteString;
    aeskey, mackey: THash256;
begin
  result := '';
  if Plain='' then
    exit;
  if not CheckCRC then
    raise EECCException.CreateUTF8('%.Encrypt: no public key',[self]);
  if Algo=ecaUnknown then // use safest algorithm by default
    if IsContentCompressed(pointer(Plain),length(Plain)) then
      Algo := ecaPBKDF2_HMAC_SHA256_AES256_CFB else
      Algo := ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ;
  if not (Algo in [Low(ECIES_AES)..High(ECIES_AES)]) then
    raise EECCException.CreateUTF8('%.Encrypt: unsupported %',[self,ToText(Algo)^]);
  try
    head.magic := THash128(ECIES_MAGIC[0]);
    head.rec := fContent.Signed.Issuer;
    head.recid := fContent.Signed.Serial;
    head.size := length(Plain);
    head.date := NowECCDate;
    head.unixts := DateTimeToUnixTime(FileDateTime);
    content := Plain;
    if Signature.Check then begin
      head.sign := Signature.fContent;
      {$ifndef NOVARIANTS}
      if Signature.InheritsFrom(TECCSignatureCertifiedFile) then
        with _Safe(TECCSignatureCertifiedFile(Signature).MetaData)^ do
          if (Kind=dvObject) and (Count>0) then begin
            head.magic := THash128(ECIES_MAGIC[1]); // indicates efMetaData
            content := ToJSON('','',jsonUnquotedPropNameCompact)+#0+content;
          end; // new format storing {metadata}+#0+plain
      {$endif}
    end else
      FillcharFast(head.sign,sizeof(head.sign),255); // Version=255=not signed
    if not ecc_make_key(head.rndpub,rndpriv) then
      raise EECCException.CreateUTF8('%.Encrypt: ecc_make_key?',[self]);
    SetLength(secret,sizeof(TECCSecretKey));
    if not ecdh_shared_secret(fContent.Signed.PublicKey,rndpriv,PECCSecretKey(secret)^) then
      raise EECCException.CreateUTF8('%.Encrypt: ecdh_shared_secret?',[self]);
    PBKDF2_HMAC_SHA256(secret,KDFSalt,KDFRounds,aeskey,'salt');
    if Algo in [low(ECIES_NOSYNLZ)..high(ECIES_NOSYNLZ)] then begin
      dec := SynLZCompress(content);
      if length(dec)>length(content) then begin // SynLZ was inefficient
        FillZero(dec);
        dec := content;
        Algo := ECIES_NOSYNLZ[Algo];
      end;
    end else
      dec := content;
    head.Algo := Algo;
    enc := ECIES_AES[Algo].SimpleEncrypt( // encrypt with PKCS7 padding
      dec,aeskey,ECIES_AESSIZE[Algo],true,true);
    PBKDF2_HMAC_SHA256(secret,MACSalt,MACRounds,mackey,'hmac');
    HMAC_SHA256(mackey,enc,head.hmac); // HMAC of the encrypted content
    head.crc := crc32c(PCardinal(@head.hmac)^,@head,sizeof(head)-sizeof(head.crc));
    SetLength(result,sizeof(head)+length(enc));
    PECIESHeader(result)^ := head;
    MoveFast(pointer(enc)^,PByteArray(result)[sizeof(head)],length(enc));
  finally
    FillZero(aeskey);
    FillZero(mackey);
    FillCharFast(rndpriv,sizeof(rndpriv),0);
    if dec<>Plain then
      FillZero(dec);
    if content<>Plain then
      FillZero(content);
    FillZero(secret);
  end;
end;

{$ifdef ISDELPHI20062007}
  {$WARNINGS ON} // circument Delphi 2007 false positive warning
{$endif}

function TECCCertificate.EncryptFile(const FileToCrypt, DestFile: TFileName;
  const Salt: RawUTF8; SaltRounds: integer; Algo: TECIESAlgo; IncludeSignFile: boolean): boolean;
var plain,encrypted: RawByteString;
    cert: TECCSignatureCertified;
    dest: TFileName;
    filetime: TDateTime;
begin
  plain := StringFromFile(FileToCrypt);
  if plain='' then
    raise EECCException.CreateUTF8('File not found: [%]',[FileToCrypt]);
  if DestFile='' then
    dest := FileToCrypt+ENCRYPTED_FILEEXT else
    dest := DestFile;
  filetime := FileAgeToDateTime(FileToCrypt);
  try
    if IncludeSignFile then
      {$ifdef NOVARIANTS}
      cert := TECCSignatureCertified.CreateFromFile(FileToCrypt,true) else
      {$else}
      cert := TECCSignatureCertifiedFile.CreateFromFile(FileToCrypt,true) else
      {$endif}
      cert := nil;
    try
      encrypted := Encrypt(plain,cert,filetime,Salt,SaltRounds,'hmac',100,Algo);
      if encrypted='' then
        result := false else
        result := FileFromString(encrypted,dest);
    finally
      cert.Free;
    end;
  finally
    FillZero(plain);
  end;
end;

{$ifndef NOVARIANTS}
function TECCCertificate.ToVariant(withBase64: boolean): variant;
begin
  result := _ObjFast(['Version',Version,'Serial',Serial,'Issuer',Issuer,
    'IssueDate',IssueDate,'ValidityStart',ValidityStart,'ValidityEnd',ValidityEnd,
    'AuthoritySerial',AuthoritySerial,'AuthorityIssuer',AuthorityIssuer,
    'IsSelfSigned',IsSelfSigned]);
  if withBase64 then
    TDocVariantData(result).AddValue('Base64',RawUTF8ToVariant(ToBase64));
end;

function TECCCertificate.ToFile(const filename: TFileName): boolean;
begin
  if CheckCRC then
   result := JSONReformatToFile(VariantSaveJSON(ToVariant),filename) else
   result := false;
end;
{$endif}


{ TECCCertificateSecret }

constructor TECCCertificateSecret.CreateNew(Authority: TECCCertificateSecret;
  const IssuerText: RawUTF8; ExpirationDays: integer; StartDate: TDateTime;
  ParanoidVerify: boolean);
var priv: PECCPrivateKey;
    pub: PECCPublicKey;
    now: TECCDate;
    sha: TSHA256;
    hash: TSHA256Digest;
    temp: TECCSignature;
    retry: boolean;
begin
  Create;
  now := NowECCDate;
  with fContent.Signed do begin
    IssueDate := now;
    if ExpirationDays>0 then begin
      if StartDate=0 then
        ValidityStart := now else
        ValidityStart := ECCDate(StartDate);
      ValidityEnd := ValidityStart+ExpirationDays;
    end;
    TAESPRNG.Fill(TAESBlock(Serial));
    if IssuerText='' then
      TAESPRNG.Fill(TAESBlock(Issuer)) else
      ECCIssuer(IssuerText,Issuer);
    for retry := false to true do begin
      if not ecc_make_key(PublicKey,fPrivateKey) then
        raise EECCException.CreateUTF8('%.CreateNew: ecc_make_key?',[self]);
      if Authority=nil then begin
        AuthoritySerial := Serial;
        AuthorityIssuer := Issuer;
        priv := @fPrivateKey; // self-signing
        pub := @PublicKey;
      end else begin
        AuthoritySerial := Authority.fContent.Signed.Serial;
        AuthorityIssuer := Authority.fContent.Signed.Issuer;
        priv := @Authority.fPrivateKey;
        pub := @Authority.fContent.Signed.PublicKey;
        if ParanoidVerify then // check below will be on Authority keys
          if not ecdsa_sign(fPrivateKey,hash,temp) or
             not ecdsa_verify(PublicKey,hash,temp) then
          if retry then
            raise EECCException.CreateUTF8('%.CreateNew: ParanoidVerify1?',[self]) else
            continue;
      end;
      sha.Full(@fContent.Signed,sizeof(TECCCertificateSigned),hash);
      if not ecdsa_sign(priv^,hash,fContent.Signature) then
        raise EECCException.CreateUTF8('%.CreateNew: ecdsa_sign?',[self]);
      if not ParanoidVerify or ecdsa_verify(pub^,hash,fContent.Signature) then
        break else
        if retry then
          raise EECCException.CreateUTF8('%.CreateNew: ParanoidVerify2?',[self]);
    end;
  end;
  fContent.CRC := fnv32(0,@fContent,sizeof(fContent)-4);
end;

constructor TECCCertificateSecret.CreateFromSecureBinary(
  const Binary: RawByteString; const PassWord: RawUTF8;
  PBKDF2Rounds: integer; AES: TAESAbstractClass);
begin
  CreateFromSecureBinary(pointer(Binary),length(Binary),PassWord,PBKDF2Rounds,AES);
end;

constructor TECCCertificateSecret.CreateFromSecureBinary(Data: pointer;
  Len: integer; const PassWord: RawUTF8; PBKDF2Rounds: integer;
  AES: TAESAbstractClass);
begin
  Create;
  if not LoadFromSecureBinary(Data,Len,PassWord,PBKDF2Rounds,AES) then
    raise EECCException.CreateUTF8('Invalid %.CreateFromSecureBinary',[self]);
end;

constructor TECCCertificateSecret.CreateFromSecureFile(
  const FileName: TFileName; const PassWord: RawUTF8;
  PBKDF2Rounds: integer; AES: TAESAbstractClass);
begin
  Create;
  if not LoadFromSecureFile(FileName,PassWord,PBKDF2Rounds,AES) then
    raise EECCException.CreateUTF8('Invalid %.CreateFromSecureFile("%")',
      [self,FileName]);
end;

constructor TECCCertificateSecret.CreateFromSecureFile(
  const FolderName: TFileName; const Serial, PassWord: RawUTF8;
  PBKDF2Rounds: integer; AES: TAESAbstractClass);
begin
  CreateFromSecureFile(IncludeTrailingPathDelimiter(FolderName)+UTF8ToString(Serial),
    PassWord,PBKDF2Rounds,AES);
end;

destructor TECCCertificateSecret.Destroy;
begin
  FillZero(fPrivateKey);
  inherited Destroy;
end;

function TECCCertificateSecret.InternalLoad(const data: RawByteString): boolean;
begin
  result := fStoreOnlyPublicKey or TAESPRNG.AFUnsplit(data,fPrivateKey,sizeof(fPrivateKey));
end;

function TECCCertificateSecret.InternalSave: RawByteString;
begin
  if fStoreOnlyPublicKey then
    result := '' else
    result := TAESPRNG.Main.AFSplit(fPrivateKey,sizeof(fPrivateKey),fAFSplitStripes);
end;

function TECCCertificateSecret.HasSecret: boolean;
begin
  result := (self<>nil) and not IsZero(THash256(fPrivateKey));
end;

const
  PRIVKEY_MAGIC: array[0..15] of AnsiChar = 'SynEccPrivatKey'#26;
  PRIVKEY_SALTSIZE = 16; // 128-bit is enough, since it is transmitted as clear

function TECCCertificateSecret.SaveToSecureBinary(const PassWord: RawUTF8;
  AFStripes, PBKDF2Rounds: integer; AES: TAESAbstractClass; NoHeader: boolean): RawByteString;
var pksav: boolean;
    stsav, head: integer;
    st: TRawByteStringStream;
    salt, enc: RawByteString;
    aeskey: TAESKey;
    a: TAESAbstract;
    e: PAnsiChar absolute result;
begin
  result := '';
  if AES=nil then
    AES := TAESCFB;
  pksav := fStoreOnlyPublicKey;
  stsav := fAFSplitStripes;
  try
    fStoreOnlyPublicKey := false;
    fAFSplitStripes := AFStripes;
    salt := TAESPRNG.Fill(PRIVKEY_SALTSIZE);
    st := TRawByteStringStream.Create;
    try
      if SaveToStream(st) then begin
        PBKDF2_HMAC_SHA256(PassWord,salt,PBKDF2Rounds,aeskey);
        a := AES.Create(aeskey);
        try
          enc := a.EncryptPKCS7(st.DataString,true);
          // result := PRIVKEY_MAGIC+salt+enc; fails under FPC :(
          if NoHeader then
            head := 0 else
            head := sizeof(PRIVKEY_MAGIC);
          SetLength(result,head+PRIVKEY_SALTSIZE+length(enc));
          MoveFast(PRIVKEY_MAGIC,e[0],head);
          XorBlock16(pointer(salt),@e[head],@PRIVKEY_MAGIC);
          MoveFast(pointer(enc)^,e[head+PRIVKEY_SALTSIZE],length(enc));
        finally
          a.Free;
        end;
      end;
    finally
      FillcharFast(pointer(st.DataString)^,length(st.DataString),0);
      st.Free;
    end;
  finally
    fStoreOnlyPublicKey := pksav;
    fAFSplitStripes := stsav;
    FillZero(aeskey);
  end;
end;

function TECCCertificateSecret.SaveToSecureFileName(FileNumber: integer): TFileName;
var tmp: RawUTF8;
begin
  if self=nil then
    result := '' else begin
    if FileNumber>0 then
      FormatUTF8('%-%',[Serial,UInt3DigitsToShort(FileNumber)],tmp) else
      tmp := Serial;
    result := UTF8ToString(tmp)+ECCCERTIFICATESECRET_FILEEXT;
  end;
end;

function TECCCertificateSecret.SaveToSecureFile(const PassWord: RawUTF8;
  const DestFolder: TFileName; AFStripes, PBKDF2Rounds: integer;
  AES: TAESAbstractClass; NoHeader: boolean): boolean;
begin
  if (self=nil) or not DirectoryExists(DestFolder) then
    result := false else
    result := FileFromString(SaveToSecureBinary(PassWord,AFStripes,PBKDF2Rounds,AES,NoHeader),
      IncludeTrailingPathDelimiter(DestFolder)+SaveToSecureFileName);
end;

function TECCCertificateSecret.SaveToSecureFiles(const PassWord: RawUTF8;
  const DestFolder: TFileName; DestFileCount, AFStripes, PBKDF2Rounds: integer;
  AES: TAESAbstractClass; NoHeader: boolean): boolean;
var diff,one: RawByteString;
    head,index,pos,difflen,onechunk,onelen: integer;
    o: PAnsiChar absolute one;
    dest: TFileName;
begin
  if DestFileCount=1 then begin
    result := SaveToSecureFile(PassWord,DestFolder,AFStripes,PBKDF2Rounds,AES,NoHeader);
    exit;
  end;
  result := false;
  dest := IncludeTrailingPathDelimiter(DestFolder);
  if (self=nil) or (DestFileCount<=0) or not DirectoryExists(dest) then
    exit;
  if DestFileCount>255 then
    DestFileCount := 255;
  diff := SaveToSecureBinary(PassWord,AFStripes*DestFileCount,PBKDF2Rounds,AES,true);
  difflen := length(diff);
  onechunk := difflen div DestFileCount;
  if NoHeader then
    head := 0 else
    head := sizeof(PRIVKEY_MAGIC);
  pos := 0;
  for index := 1 to DestFileCount do begin
    if index<DestFileCount then
      onelen := onechunk else
      onelen := difflen-pos;
    SetLength(one,head+2+onelen);
    MoveFast(PRIVKEY_MAGIC,PByteArray(one)^[0],head);
    PByteArray(one)^[head] := index;
    PByteArray(one)^[head+1] := DestFileCount;
    MoveFast(PByteArray(diff)[pos],PByteArray(one)^[head+2],onelen);
    inc(pos,onelen);
    if not FileFromString(one,SaveToSecureFileName(index)) then
      exit;
  end;
  result := true;
end;

function TECCCertificateSecret.LoadFromSecureBinary(const Binary: RawByteString;
  const PassWord: RawUTF8; PBKDF2Rounds: integer; AES: TAESAbstractClass): boolean;
begin
  result := LoadFromSecureBinary(pointer(Binary),length(Binary),PassWord,PBKDF2Rounds,AES);
end;

function TECCCertificateSecret.LoadFromSecureBinary(Data: pointer; Len: integer;
  const PassWord: RawUTF8; PBKDF2Rounds: integer; AES: TAESAbstractClass): boolean;
var salt,decrypted: RawByteString;
    st: TRawByteStringStream;
    aeskey: TAESKey;
    head: integer;
    a: TAESAbstract;
begin
  result := false;
  dec(Len,PRIVKEY_SALTSIZE);
  if (self=nil) or (Len<=sizeof(PRIVKEY_MAGIC)+sizeof(TAESBlock)) then
    exit;
  if IsEqual(THash128(PRIVKEY_MAGIC),PHash128(Data)^) then begin
    dec(len,16);
    head := 16;
  end else
    head := 0; // was with NoHeader=true (e.g. SaveToSource)
  if Len and AESBlockMod<>0 then
    exit;
  SetString(salt,PAnsiChar(Data)+head,PRIVKEY_SALTSIZE);
  try
    XorBlock16(pointer(salt),@PRIVKEY_MAGIC);
    PBKDF2_HMAC_SHA256(PassWord,salt,PBKDF2Rounds,aeskey);
    if AES=nil then
      AES := TAESCFB;
    a := AES.Create(aeskey);
    try
      decrypted := a.DecryptPKCS7Buffer(PAnsiChar(Data)+head+PRIVKEY_SALTSIZE,Len,true,false);
      if decrypted='' then
        exit; // invalid content
    finally
      a.Free;
    end;
    st := TRawByteStringStream.Create(decrypted);
    try
      if LoadFromStream(st) then
        result := ECCCheck(fContent) and not IsZero(THash256(fPrivateKey));
    finally
      st.Free;
    end;
  finally
    FillZero(decrypted);
    FillZero(aeskey);
  end;
end;

function TECCCertificateSecret.LoadFromSecureFile(
  const FileName: TFileName; const PassWord: RawUTF8;
  PBKDF2Rounds: integer; AES: TAESAbstractClass): boolean;
var FN: TFileName;
begin
  if ExtractFileExt(FileName)='' then
    FN := FileName+ECCCERTIFICATESECRET_FILEEXT else
    FN := FileName;
  result := LoadFromSecureBinary(StringFromFile(FN),PassWord,PBKDF2Rounds,AES);
end;

function TECCCertificateSecret.SaveToSource(const ConstName, Comment, PassWord: RawUTF8;
  IncludePassword: boolean; AFStripes, PBKDF2Rounds: integer; AES: TAESAbstractClass;
  IncludeRaw: boolean): RawUTF8;
var data: RawByteString;
    name,suffix: RawUTF8;
begin
  result := '';
  if (self=nil) or (Password='') then
    exit;
  data := SaveToSecureBinary(Password,AFStripes,PBKDF2Rounds,AES,true); // NoHeader=true
  if data='' then
    exit;
  if ConstName='' then
    name := '_'+copy(Serial,1,24) else
    name := UpperCase(ConstName);
  if IncludePassword then
    suffix := FormatUTF8('  %_PASS = %;'#13#10'  %_CYPH = ''%'';'#13#10,
      [name,QuotedStr(PassWord),name,TSynPersistentWithPassword.ComputePassword(PassWord)]);
  if ConstName<>'' then
    suffix := FormatUTF8('  %_SERIAL = ''%'';'#13#10'%',[name,Serial,suffix]);
  suffix := FormatUTF8('  %_ROUNDS = %;'#13#10'%',[name,PBKDF2Rounds,suffix]);
  if IncludeRaw then
    suffix := FormatUTF8('  %_RAW = ''%'';'#13#10'%',[name,
      SynCommons.BinToHex(@fPrivateKey,sizeof(fPrivateKey)),suffix]);
  result := BinToSource(name,Comment,pointer(data),length(data),16,suffix)
end;

function TECCCertificateSecret.SignToBase64(Data: pointer; Len: integer): RawUTF8;
begin
  if (Data=nil) or (Len<0) then
    result := '' else
    result := SignToBase64(SHA256Digest(Data,Len));
end;

function TECCCertificateSecret.SignToBase64(const Hash: THash256): RawUTF8;
var sign: TECCSignatureCertified;
begin
  result := '';
  if (self=nil) or IsZero(Hash) then
    exit;
  sign := TECCSignatureCertified.CreateNew(self,Hash);
  try
    result := sign.ToBase64;
  finally
    sign.Free;
  end;
end;

{$ifndef NOVARIANTS}
function TECCCertificateSecret.SignFile(const FileToSign: TFileName;
  const MetaNameValuePairs: array of const): TFileName;
var content: RawByteString;
    sign: RawUTF8;
    doc, meta: TDocVariantData;
    sha: TSHA256Digest;
begin
  content := StringFromFile(FileToSign);
  if content='' then
    raise EECCException.CreateUTF8('%.SignFile: file [%] not found',[self,FileToSign]);
  sha := SHA256Digest(pointer(content),length(content));
  sign := SignToBase64(sha);
  meta.InitObject(['name',ExtractFileName(FileToSign),
    'date',DateTimeToIso8601Text(FileAgeToDateTime(FileToSign))],JSON_OPTIONS_FAST);
  meta.AddNameValuesToObject(MetaNameValuePairs);
  doc.InitObject([
    'meta',variant(meta), 'size',length(content), 'md5',MD5(content),
    'sha256',SHA256DigestToString(sha), 'sign',sign],JSON_OPTIONS_FAST);
  result := FileToSign+ECCCERTIFICATESIGN_FILEEXT;
  FileFromString(doc.ToJSON('','',jsonHumanReadable),result);
end;
{$endif}

function TECCCertificateSecret.Decrypt(
  const Encrypted: RawByteString; out Decrypted: RawByteString;
  Signature: PECCSignatureCertifiedContent; MetaData: PRawJSON;
  FileDateTime: PDateTime;
  const KDFSalt: RawUTF8; KDFRounds: integer;
  const MACSalt: RawUTF8; MACRounds: integer): TECCDecrypt;
var head: TECIESHeader;
    features: TECIESFeatures;
    data: PAnsiChar;
    datalen,metaend: integer;
    secret,enc,dec,temp: RawByteString;
    aeskey, mackey: THash256;
    hmac: THash256;
begin
  result := ecdCorrupted;
  datalen := length(Encrypted)-sizeof(TECIESHeader);
  if (datalen<=0) or not ECIESHeader(Encrypted,head) then
    exit;
  data := @PByteArray(Encrypted)[sizeof(TECIESHeader)];
  if CheckCRC and HasSecret then
  try
    if not IsEqual(head.recid,fContent.Signed.Serial) then begin
      result := ecdInvalidSerial;
      exit;
    end;
    SetLength(secret,sizeof(TECCSecretKey));
    if not ecdh_shared_secret(head.rndpub,fPrivateKey,PECCSecretKey(secret)^) then
      exit;
    PBKDF2_HMAC_SHA256(secret,MACSalt,MACRounds,mackey,'hmac');
    HMAC_SHA256(@mackey,data,sizeof(mackey),datalen,hmac);
    result := ecdInvalidMAC;
    if not IsEqual(hmac,head.hmac) then
      exit;
    PBKDF2_HMAC_SHA256(secret,KDFSalt,KDFRounds,aeskey,'salt');
    SetString(enc,data,datalen);
    dec := ECIES_AES[head.Algo].SimpleEncrypt(
      enc,aeskey,ECIES_AESSIZE[head.Algo],false,true);
    if head.Algo in [low(ECIES_NOSYNLZ)..high(ECIES_NOSYNLZ)] then
      SynLZDecompress(pointer(dec),length(dec),Decrypted) else
      Decrypted := dec;
    result := ecdDecryptError;
    features := ECIESFeatures(head);
    if efMetaData in features then begin
      if (Decrypted='') or (Decrypted[1]<>'{') then
        exit;
      metaend := PosExChar(#0,Decrypted); // {metadata}+#0+plain
      if metaend=0 then
        exit;
      if MetaData<>nil then
        MetaData^ := copy(Decrypted,1,metaend-1);
      temp := copy(Decrypted,metaend+1,maxInt);
      FillZero(Decrypted); // avoid uncyphered content on heap
      Decrypted := temp;
    end;
    if cardinal(length(Decrypted))<>head.size then
       exit;
    if FileDateTime<>nil then
      if head.unixts = 0 then
        FileDateTime^ := 0 else
        FileDateTime^ := UnixTimeToDateTime(head.unixts);
    if (Signature<>nil) and ECCCheck(head.sign) then begin
      result := ecdDecryptedWithSignature;
      Signature^ := head.sign;
    end else
      result := ecdDecrypted;
  finally
    FillZero(aeskey);
    FillZero(mackey);
    FillZero(secret);
    if dec<>Decrypted then
      FillZero(dec);
  end else
    result := ecdNoPrivateKey;
end;

function TECCCertificateSecret.DecryptFile(const FileToDecrypt,
  DestFile: TFileName; const Salt: RawUTF8; SaltRounds: integer;
  Signature: PECCSignatureCertifiedContent; MetaData: PRawJSON): TECCDecrypt;
var content,plain: RawByteString;
    dest: TFileName;
    filetime: TDateTime;
begin
  content := StringFromFile(FileToDecrypt);
  result := ecdNoContent;
  if content<>'' then
  try
    result := ecdNoPrivateKey;
    if not CheckCRC then
      exit;
    if DestFile='' then
      dest := GetFileNameWithoutExt(FileToDecrypt) else
      dest := DestFile;
    result := Decrypt(content,plain,Signature,MetaData,@filetime,Salt,SaltRounds);
    if result in ECC_VALIDDECRYPT then
      if not FileFromString(plain,dest,false,filetime) then
        result := ecdWriteFileError;
  finally
    FillZero(plain);
  end;
end;


{ TECCCertificateSecretSetting }

function TECCCertificateSecretSetting.CertificateSecret(
  const FolderName: TFileName): TECCCertificateSecret;
begin
  if self=nil then
    result := nil else
    if FileName<>'' then
      result := TECCCertificateSecret.CreateFromSecureFile(
        FileName,GetPassWordPlain,PasswordRounds) else
    if Serial='' then
      result := nil else
      result := TECCCertificateSecret.CreateFromSecureFile(
        FolderName,Serial,GetPassWordPlain,PasswordRounds);
end;

constructor TECCCertificateSecretSetting.Create;
begin
  inherited Create;
  fPasswordRounds := DEFAULT_ECCROUNDS;
end;


{ TECCCertificateDecryptSetting }

constructor TECCCertificateDecryptSetting.Create;
begin
  inherited Create;
  fSalt := 'salt';
  fSaltRounds := DEFAULT_ECCROUNDS;
end;


{ TECCSignatureCertified }

constructor TECCSignatureCertified.Create;
begin
  inherited Create;
  fContent.Version := 1;
end;

constructor TECCSignatureCertified.CreateFrom(
  const binary: TECCSignatureCertifiedContent; NoException: boolean);
begin
  Create;
  if ECCCheck(binary) then
    fContent := binary else
    if not NoException then
      raise EECCException.CreateUTF8('Invalid %.CreateFrom',[self]);
end;

constructor TECCSignatureCertified.CreateFromBase64(const base64: RawUTF8;
  NoException: boolean);
begin
  Create;
  if not FromBase64(base64) then
    if not NoException then
      raise EECCException.CreateUTF8('Invalid %.CreateFromBase64',[self]);
end;

constructor TECCSignatureCertified.CreateFromFile(const signfilename: TFileName;
  NoException: boolean);
begin
  Create;
  if not FromFile(signfilename) then
    if not NoException then
      raise EECCException.CreateUTF8('Invalid %.CreateFromFile("%")',
        [self,signfilename]);
end;

constructor TECCSignatureCertified.CreateNew(
  Authority: TECCCertificateSecret; Data: pointer; Len: integer);
begin
  CreateNew(Authority,SHA256Digest(Data,Len));
end;

constructor TECCSignatureCertified.CreateNew(
  Authority: TECCCertificateSecret; const Hash: THash256);
begin
  Create;
  if not Authority.HasSecret then
    raise EECCException.CreateUTF8('%.CreateNew: secret=0 %',[self,Authority]);
  if IsZero(Hash) then
    raise EECCException.CreateUTF8('%.CreateNew(Hash=0)',[self]);
  fContent.Date := NowECCDate;
  fContent.AuthoritySerial := Authority.Content.Signed.Serial;
  fContent.AuthorityIssuer := Authority.Content.Signed.Issuer;
  if not ecdsa_sign(Authority.fPrivateKey,Hash,fContent.Signature) then
    raise EECCException.CreateUTF8('%.CreateNew: ecdsa_sign?',[self]);
end;

function TECCSignatureCertified.GetAuthorityIssuer: RawUTF8;
begin
  result := ECCText(fContent.AuthorityIssuer);
end;

function TECCSignatureCertified.GetAuthoritySerial: RawUTF8;
begin
  result := ECCText(fContent.AuthoritySerial);
end;

function TECCSignatureCertified.GetDate: RawUTF8;
begin
  result := ECCText(fContent.Date);
end;

function TECCSignatureCertified.FromBase64(const base64: RawUTF8): boolean;
begin
  result := (self<>nil) and
    Base64ToBin(pointer(base64),@fContent,length(base64),sizeof(fContent),false) and
    ECCCheck(fContent);
end;

function TECCSignatureCertified.FromFile(const signfilename: TFileName): boolean;
var json: RawUTF8;
begin
  if FileExists(signfilename+ECCCERTIFICATESIGN_FILEEXT) then
    json := StringFromFile(signfilename+ECCCERTIFICATESIGN_FILEEXT) else
    json := StringFromFile(signfilename);
  if json='' then
    result := false else
    result := FromBase64(JSONDecode(json,'sign',nil,true));
end;

function TECCSignatureCertified.ToBase64: RawUTF8;
begin
  result := BinToBase64(@fContent,sizeof(fContent));
end;

{$ifndef NOVARIANTS}
function TECCSignatureCertified.ToVariant: variant;
begin
  result := _ObjFast(['Version',Version,'Date',Date,
    'AuthoritySerial',AuthoritySerial,'AuthorityIssuer',AuthorityIssuer]);
end;
{$endif}

function TECCSignatureCertified.Check: boolean;
begin
  result := (self<>nil) and ECCCheck(fContent);
end;

function TECCSignatureCertified.Verify(Authority: TECCCertificate;
  const hash: THash256): TECCValidity;
begin
  if self=nil then
    result := ecvBadParameter else
  if not Authority.CheckCRC then
    result := ecvUnknownAuthority else
    result := ECCVerify(fContent,hash,Authority.fContent);
end;

function TECCSignatureCertified.Verify(Authority: TECCCertificate;
  Data: pointer; Len: integer): TECCValidity;
begin
  result := Verify(Authority,SHA256Digest(Data,Len));
end;

function TECCSignatureCertified.SaveToDERBinary: RawByteString;
var der: TEccSignatureDer;
begin
  if not Check then
    result := '' else
    SetString(result,PAnsiChar(@der),EccSignToDer(fContent.Signature,der));
end;

function TECCSignatureCertified.SaveToDERFile(
  const FileName: TFileName): boolean;
begin
  if not Check then
    result := false else
    result := FileFromString(SaveToDERBinary, FileName);
end;


{$ifndef NOVARIANTS}

{ TECCSignatureCertifiedFile }

function TECCSignatureCertifiedFile.FromFile(const aFileName: TFileName): boolean;
var json: RawUTF8;
begin
  if SameText(ExtractFileExt(aFileName),ECCCERTIFICATESIGN_FILEEXT) then
    json := StringFromFile(aFileName) else
    json := StringFromFile(aFileName+ECCCERTIFICATESIGN_FILEEXT);
  result := FromFileJson(json);
end;

function TECCSignatureCertifiedFile.FromFileJson(const aFileContent: RawUTF8): boolean;
begin
  fLowLevelInfo.Clear;
  if not fLowLevelInfo.InitJSON(aFileContent,JSON_OPTIONS_FAST) then begin
    result := false;
    exit;
  end;
  fSize := fLowLevelInfo.I['size'];
  fMetaData := fLowLevelInfo.GetValueOrEmpty('meta'); // Value[] makes GPF
  fMD5 := fLowLevelInfo.U['md5'];
  fSHA256 := fLowLevelInfo.U['sha256'];
  result := (fSize>0) and (_Safe(fMetaData)^.Kind<>dvArray) and
    MD5StringToDigest(fMD5,fMD5Digest) and
    SHA256StringToDigest(fSHA256,fSHA256Digest) and
    FromBase64(fLowLevelInfo.U['sign']);
end;

function TECCSignatureCertifiedFile.FromDecryptedFile(const aDecryptedContent: RawByteString;
  const Signature: TECCSignatureCertifiedContent; const MetaData: RawJSON): boolean;
var MD5: TMD5;
    SHA256: TSHA256;
begin
  result := ECCCheck(Signature);
  if not result then
    exit;
  fSize := length(aDecryptedContent);
  MD5.Full(pointer(aDecryptedContent),fSize,fMD5Digest);
  fMD5 := MD5DigestToString(fMD5Digest);
  SHA256.Full(pointer(aDecryptedContent),fSize,fSHA256Digest);
  fSHA256 := SHA256DigestToString(fSHA256Digest);
  fMetaData := _JsonFast(MetaData);
  fContent := Signature;
  fLowLevelInfo.Clear;
  fLowLevelInfo.InitObject(['size',fSize,'md5',fMD5,'sha256',fSHA256,
    'sign',ToBase64,'meta',fMetaData]);
end;

constructor TECCSignatureCertifiedFile.CreateFromDecryptedFile(
  const aDecryptedContent: RawByteString;
  const Signature: TECCSignatureCertifiedContent; const MetaData: RawJSON);
begin
  inherited Create;
  if not FromDecryptedFile(aDecryptedContent,Signature,MetaData) then
      raise EECCException.CreateUTF8('Invalid Signature for %.CreateFromDecryptedFile',[self]);
end;

{$endif NOVARIANTS}


{ TECCCertificateChain }

constructor TECCCertificateChain.CreateFromJson(const json: RawUTF8);
begin
  Create;
  if not LoadFromJson(json) then
    raise EECCException.CreateUTF8('Invalid %.CreateFromJson',[self]);
end;

constructor TECCCertificateChain.CreateFromArray(const values: TRawUTF8DynArray);
begin
  Create;
  if not LoadFromArray(values) then
    raise EECCException.CreateUTF8('Invalid %.CreateFromArray',[self]);
end;

destructor TECCCertificateChain.Destroy;
begin
  ObjArrayClear(fItems);
  inherited;
end;

function TECCCertificateChain.IsValid(cert: TECCCertificate): TECCValidity;
begin
  if (self=nil) or (cert=nil) then
    result := ecvBadParameter else
    result := IsValid(cert.Content);
end;

function TECCCertificateChain.IsValid(const content: TECCCertificateContent;
  ignoreDate: boolean): TECCValidity;
var auth: TECCCertificateContent;
    sha: TSHA256;
    hash: TSHA256Digest;
    crc: Int64;
begin
  result := ecvCorrupted;
  if not ECCCheck(content) then
    exit;
  if not ignoreDate then begin
    result := ecvInvalidDate;
    if not ECCCheckDate(content) then
      exit;
  end;
  if ECCSelfSigned(content) then
    result := ecvValidSelfSigned else
    result := ecvValidSigned;
  if fIsValidCached then begin
    crc := crc64c(@content,sizeof(content));
    fSafe.Lock;
    try
      if Int64ScanExists(pointer(fIsValidCache),fIsValidCacheCount,crc) then
        exit;
    finally
      fSafe.Unlock;
    end;
  end else
    crc := 0;
  if result=ecvValidSelfSigned then
    auth.Signed.PublicKey := content.Signed.PublicKey else
    if not GetBySerial(content.Signed.AuthoritySerial,auth) then begin
      result := ecvUnknownAuthority;
      exit;
    end else
    if not ECCCheckDate(auth) then begin
      result := ecvDeprecatedAuthority;
      exit;
    end;
  sha.Full(@content.Signed,sizeof(content.Signed),hash);
  if ecdsa_verify(auth.Signed.PublicKey,hash,content.Signature) then begin
    fSafe.Lock;
    try
      if fIsValidCached and (crc<>0) then
        AddInt64(fIsValidCache,fIsValidCacheCount,crc);
    finally
      fSafe.Unlock;
    end;
  end else
    result := ecvInvalidSignature;
end;

function TECCCertificateChain.IndexBySerial(const Serial: TECCCertificateID): integer;
var ser: THash128Rec absolute Serial;
begin
  if (self<>nil) and ((ser.Lo<>0) or (ser.Hi<>0)) then begin
    for result := 0 to length(fItems)-1 do
      with PHash128Rec(@fItems[result].Signed.Serial)^ do
        {$ifdef CPU64}
        if (ser.Lo=Lo) and (ser.Hi=Hi) then
        {$else}
        if (ser.i0=i0) and (ser.i1=i1) and (ser.i2=i2) and (ser.i3=i3) then
        {$endif}
          exit;
  end;
  result := -1;
end;

function TECCCertificateChain.GetBySerial(const Serial: TECCCertificateID): TECCCertificate;
var i: integer;
begin
  i := IndexBySerial(Serial);
  if i<0 then
    result := nil else
    result := fItems[i];
end;

function TECCCertificateChain.GetBySerial(const Serial: RawUTF8): TECCCertificate;
var id: TECCCertificateID;
begin
  if ECCID(Serial,id) then
    result := GetBySerial(id) else
    result := nil;
end;

function TECCCertificateChain.GetBySerial(const Serial: TECCCertificateID;
  out Content: TECCCertificateContent): boolean;
var cert: TECCCertificate;
begin
  fSafe.Lock;
  try
    cert := GetBySerial(Serial);
    if cert<>nil then begin
      Content := cert.Content;
      result := true;
    end else
      result := false;
  finally
    fSafe.UnLock;
  end;
end;

function TECCCertificateChain.GetBySerial(const Serial: TECCCertificateID;
  out PublicKey: TECCPublicKey): boolean;
var cert: TECCCertificate;
begin
  fSafe.Lock;
  try
    cert := GetBySerial(Serial);
    if cert<>nil then begin
      PublicKey := cert.Content.Signed.PublicKey;
      result := true;
    end else
      result := false;
  finally
    fSafe.UnLock;
  end;
end;

procedure TECCCertificateChain.SetIsValidCached(const Value: boolean);
begin
  if fIsValidCached=Value then
    exit;
  fSafe.Lock;
  try
    fIsValidCached := Value;
    if not Value then begin
      fIsValidCache := nil;
      fIsValidCacheCount := 0;
    end;
  finally
    fSafe.UnLock;
  end;
end;

function TECCCertificateChain.InternalAdd(cert: TECCCertificate;
  expected: TECCValidity): integer;
begin
  result := -1;
  if (self=nil) or (cert=nil) or
     (IsValid(cert.fContent,true)<>expected) then
    exit;
  fSafe.Lock;
  try
    if IndexBySerial(cert.Signed.Serial)<0 then
      result := ObjArrayAdd(fItems,cert);
  finally
    fSafe.UnLock;
  end;
end;

function TECCCertificateChain.Add(cert: TECCCertificate): integer;
begin
  result := InternalAdd(cert,ecvValidSigned);
end;

function TECCCertificateChain.AddSelfSigned(cert: TECCCertificate): integer;
begin
  result := InternalAdd(cert,ecvValidSelfSigned);
end;

procedure TECCCertificateChain.Clear;
begin
  fSafe.Lock;
  try
    ObjArrayClear(fItems);
    fIsValidCacheCount := 0;
    fIsValidCache := nil;
  finally
    fSafe.UnLock;
  end;
end;

function TECCCertificateChain.GetCount: integer;
begin
  if self=nil then
    result := 0 else
    result := length(fItems);
end;

function TECCCertificateChain.IsAuthorized(sign: TECCSignatureCertified): boolean;
begin
  if (self<>nil) and (sign<>nil) then
    result := IsAuthorized(sign.Content) else
    result := false;
end;

function TECCCertificateChain.IsAuthorized(
  const sign: TECCSignatureCertifiedContent): boolean;
var content: TECCCertificateContent;
begin
  result := GetBySerial(sign.AuthoritySerial, content) and
    IsEqual(content.Signed.AuthorityIssuer, sign.AuthorityIssuer);
end;

function TECCCertificateChain.IsAuthorized(const base64sign: RawUTF8): boolean;
var sign: TECCSignatureCertifiedContent;
begin
  if ECCSign(base64sign,sign) then
    result := IsAuthorized(sign) else
    result := false;
end;

function TECCCertificateChain.IsSigned(sign: TECCSignatureCertified;
  Data: pointer; Len: integer): TECCValidity;
var hash: TSHA256Digest;
begin
  if (self<>nil) and (sign<>nil) and (Data<>nil) and (Len>0) then begin
    hash := SHA256Digest(Data,Len);
    {$ifndef NOVARIANTS}
    if sign.InheritsFrom(TECCSignatureCertifiedFile) then
      with TECCSignatureCertifiedFile(sign) do
        if (Size<>Len) or not IsEqual(hash,SHA256Digest) or
           not IsEqual(MD5Buf(Data^,Len),MD5Digest) then begin
          result := ecvCorrupted;
          exit;
        end;
    {$endif}
    result := IsSigned(sign.Content,hash);
  end else
    result := ecvBadParameter;
end;

{$ifndef NOVARIANTS}
function TECCCertificateChain.IsSigned(sign: TECCSignatureCertifiedFile): TECCValidity;
begin
  if (self<>nil) and (sign<>nil) then
    result := IsSigned(sign.Content,sign.SHA256Digest) else
    result := ecvBadParameter;
end;
{$endif NOVARIANTS}

function TECCCertificateChain.IsSigned(sign: TECCSignatureCertified;
  const hash: THash256): TECCValidity;
begin
  if (self<>nil) and (sign<>nil) then
    result := IsSigned(sign.Content,hash) else
    result := ecvBadParameter;
end;

function TECCCertificateChain.IsSigned(const sign: TECCSignatureCertifiedContent;
  Data: pointer; Len: integer): TECCValidity;
begin
  if (Data=nil) or (Len<=0) then
    result := ecvBadParameter else
    result := IsSigned(sign,SHA256Digest(Data,Len));
end;

function TECCCertificateChain.IsSigned(const base64sign: RawUTF8;
  const hash: THash256): TECCValidity;
var sign: TECCSignatureCertifiedContent;
begin
  if ECCSign(base64sign,sign) then
    result := IsSigned(sign,hash) else
    result := ecvBadParameter;
end;

function TECCCertificateChain.IsSigned(const base64sign: RawUTF8;
  Data: pointer; Len: integer): TECCValidity;
var sign: TECCSignatureCertifiedContent;
begin
  if ECCSign(base64sign,sign) then
    result := IsSigned(sign,Data,Len) else
    result := ecvBadParameter;
end;

function TECCCertificateChain.IsSigned(const sign: TECCSignatureCertifiedContent;
  const hash: THash256): TECCValidity;
var auth: TECCCertificateContent;
begin
  if self=nil then
    result := ecvBadParameter else
  if not GetBySerial(sign.AuthoritySerial,auth) then
    result := ecvUnknownAuthority else
    result := ECCVerify(sign,hash,auth);
end;

function TECCCertificateChain.SaveToJson: RawUTF8;
begin
  result := JSONEncodeArrayUTF8(SaveToArray);
end;

function TECCCertificateChain.SaveToArray: TRawUTF8DynArray;
var i: integer;
begin
  fSafe.Lock;
  try
    SetLength(result,length(fItems));
    for i := 0 to high(result) do
      result[i] := fItems[i].PublicToBase64;
  finally
    fSafe.UnLock;
  end;
end;

function TECCCertificateChain.LoadFromJson(const json: RawUTF8): boolean;
var values: TRawUTF8DynArray;
    tmp: TSynTempBuffer; // private copy
begin
  tmp.Init(json);
  try
    result := (DynArrayLoadJSON(values,tmp.buf,TypeInfo(TRawUTF8DynArray))<>nil) and
      LoadFromArray(values);
  finally
    tmp.Done;
  end;
end;

function TECCCertificateChain.LoadFromArray(const values: TRawUTF8DynArray): boolean;
var i: integer;
begin
  result := false;
  if self=nil then
    exit;
  fSafe.Lock;
  try
    Clear;
    SetLength(fItems,length(values));
    for i := 0 to high(values) do begin
      fItems[i] := TECCCertificate.Create;
      if not fItems[i].FromBase64(values[i]) then begin
        ObjArrayClear(fItems);
        exit;
      end;
    end;
  finally
    fSafe.UnLock;
  end;
  result := true;
end;

function TECCCertificateChain.ValidateItems: TECCCertificateObjArray;
var i: integer;
begin
  result := nil;
  if self=nil then
    exit;
  fSafe.Lock;
  try
    for i := 0 to high(fItems) do
      if not (IsValid(fItems[i]) in ECC_VALIDSIGN) then
        ObjArrayAdd(result,fItems[i]);
  finally
    fSafe.UnLock;
  end;
end;

{$ifndef NOVARIANTS} // uses TDocVariantData for JSON serialization

constructor TECCCertificateChain.CreateFromFile(const jsonfile: TFileName);
begin
  Create;
  if not LoadFromFile(jsonfile) then
    raise EECCException.CreateUTF8('Invalid %.CreateFromFile("%")',[self,jsonfile]);
end;

constructor TECCCertificateChain.CreateFromFiles(const files: array of TFileName);
var i: integer;
    auth: TECCCertificate;
begin
  Create;
  for i := 0 to high(files) do begin
    auth := TECCCertificate.Create;
    try
      if auth.FromFile(files[i]) then begin
        ObjArrayAdd(fItems,auth);
        auth := nil;
      end else
        raise EECCException.CreateUTF8(
          '%.CreateFromFiles: invalid file [%]',[self,files[i]]);
    finally
      auth.Free;
    end;
  end;
end;

function TECCCertificateChain.SaveToFileVariant: variant;
var pub64,items: TDocVariantData;
    i,n: integer;
begin
  fSafe.Lock;
  try
    n := length(fItems);
    pub64.InitFast(n,dvArray);
    items.InitFast(n,dvArray);
    for i := 0 to n-1 do begin
      pub64.AddItemText(fItems[i].PublicToBase64);
      items.AddItem(fItems[i].ToVariant(false));
    end;
    result := _ObjFast(['PublicBase64',variant(pub64),'Items',variant(items)]);
  finally
    fSafe.UnLock;
  end;
end;

function TECCCertificateChain.SaveToFileContent: RawUTF8;
begin
  VariantSaveJSON(SaveToFileVariant,twJSONEscape,result);
end;

function TECCCertificateChain.LoadFromFileContent(const cajsoncontent: RawUTF8): boolean;
var doc: TDocVariantData;
    values: TRawUTF8DynArray;
begin
  result := false;
  if doc.InitJSON(cajsoncontent,JSON_OPTIONS_FAST) then begin
    doc.GetAsDocVariantSafe('PublicBase64')^.ToRawUTF8DynArray(values);
    result := LoadFromArray(values);
  end;
end;

function GetChainFileName(const jsonfile: TFileName): TFileName;
begin
  if ExtractFileExt(jsonfile)='' then
    result := jsonfile+ECCCERTIFICATES_FILEEXT else
    result := jsonfile;
end;

function TECCCertificateChain.SaveToFile(const jsonfile: TFileName): boolean;
var json: RawUTF8;
begin
  if (Count=0) or (jsonfile='') then
    result := false else begin
    json := SaveToFileContent;
    result := JSONBufferReformatToFile(pointer(json),GetChainFileName(jsonfile));
  end;
end;

function TECCCertificateChain.LoadFromFile(const jsonfile: TFileName): boolean;
var json: RawUTF8;
    fn: TFileName;
begin
  fn := GetChainFileName(jsonfile);
  json := StringFromFile(fn);
  if json='' then
    json := StringFromFile(ECCKeyFileFolder+fn);
  if json='' then
    result := false else
    result := LoadFromFileContent(json);
end;

{$endif NOVARIANTS}


{ TECDHEProtocol }

constructor TECDHEProtocol.Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain;
  aPrivate: TECCCertificateSecret);
var res: TECCValidity;
begin
  if (aPKI<>nil) and (aPrivate<>nil) then begin
    res := aPKI.IsValid(aPrivate);
    if not (res in ECC_VALIDSIGN) then
      raise EECCException.CreateUTF8('%.Create failed: aPKI.IsValid(%)=%',
        [self,aPrivate.Serial,ToText(res)^]);
  end;
  inherited Create;
  fAlgo.auth := aAuth;
  fPKI := aPKI;
  fPrivate := aPrivate;
  fEFSalt := 'ecdhesalt';
  fMACSalt := 'ecdhemac';
end;

constructor TECDHEProtocol.CreateFrom(aAnother: TECDHEProtocol);
begin
  Create(aAnother.fAlgo.auth,aAnother.fPKI,aAnother.fPrivate);
  fEFSalt := aAnother.fEFSalt;
  fMACSalt := aAnother.fMACSalt;
end;

var
  _FromKeySetCA: TECCCertificateChain;
  _FromKeySetCARefCount: integer;

destructor TECDHEProtocol.Destroy;
begin
  if fAES[true]<>fAES[false] then
    fAES[true].Free; // occurs only for TAESCBC
  fAES[false].Free;
  FillZero(fkM[false].b);
  FillZero(fkM[true].b);
  if fPKI<>nil then
    if ownPKI in fOwned then
      fPKI.Free else
    if (fPKI=_FromKeySetCA) and (_FromKeySetCARefCount>0) then
      dec(_FromKeySetCARefCount);
  if ownPrivate in fOwned then
    fPrivate.Free;
  inherited Destroy;
end;

class procedure TECDHEProtocol.FromKeySetCA(aPKI: TECCCertificateChain);
begin
  if _FromKeySetCA<>nil then
    if _FromKeySetCARefCount>0 then
      raise EECCException.CreateUTF8('%.FromKeySetCA: % is still used by % instance(s)',
        [self,_FromKeySetCA,_FromKeySetCARefCount]) else
    _FromKeySetCA.Free;
  _FromKeySetCA := aPKI;
end;

class function TECDHEProtocol.FromKey(const aKey: RawUTF8; aServer: boolean): TECDHEProtocol;
const CL: array[boolean] of TECDHEProtocolClass = (
  TECDHEProtocolServer, TECDHEProtocolClient);
var sw: TSynNameValue;
    pw,c: RawUTF8;
    fn: TFileName;
    algo: TECDHEAlgo;
    ca: TECCCertificateChain;
    chain: TRawUTF8DynArray;
    priv: TECCCertificateSecret;
    i,pr: integer;
begin
  result := nil;
  if not IdemPChar(pointer(aKey),'A=') then
    exit;
  // a=mutual;k=hmacsha256;e=aescrc128;m=duringef;p=34a2;pw=password;pr=60000;ca=..
  sw.InitFromCSV(pointer(aKey),'=',';');
  if not sw.ValueEnum('a',TypeInfo(TECDHEAuth),algo.auth) then
    exit; // mandatory parameter
  sw.ValueEnum('k',TypeInfo(TECDHEKDF),algo.kdf);
  sw.ValueEnum('e',TypeInfo(TECDHEEF),algo.ef);
  sw.ValueEnum('m',TypeInfo(TECDHEMAC),algo.mac);
  // compute ca: TECCCertificateChain
  ca := nil;
  c := sw.Str['ca'];
  if c<>'' then begin
    ca := TECCCertificateChain.Create;
    {$ifndef NOVARIANTS}
    fn := UTF8ToString(c);
    if not ca.LoadFromFile(fn) then
    {$endif NOVARIANTS} begin
      CSVToRawUTF8DynArray(c,',','',chain);
      for i := 0 to high(chain) do
        chain[i] := UnQuoteSQLString(chain[i]);
      if ca.LoadFromArray(chain) then
        ca.IsValidCached := true else // for faster Clone process
        FreeAndnil(ca);
    end;
  end;
  if (ca=nil) and (_FromKeySetCA<>nil) then begin
    ca := _FromKeySetCA;
    inc(_FromKeySetCARefCount);
  end;
  // compute priv: TECCCertificateSecret
  priv := nil;
  fn := UTF8ToString(sw.Str['p']);
  pw := sw.Str['pw'];
  pr := sw.ValueInt('pr',60000); // DEFAULT_ECCROUNDS may change
  if (fn<>'') and (pw<>'') and ECCKeyFileFind(fn,true) then
    priv := TECCCertificateSecret.CreateFromSecureFile(fn,pw,pr);
  result := CL[aServer].Create(algo.auth,ca,priv);
  result.KDF := algo.kdf;
  result.EF := algo.ef;
  result.MAC := algo.mac;
  if (ca<>nil) and (ca<>_FromKeySetCA) then
    include(result.fOwned,ownPKI);
  if priv<>nil then
    include(result.fOwned,ownPrivate);
end;

class function TECDHEProtocol.FromKeyCompute(const privkey,privpassword: RawUTF8;
  privrounds: integer; const pki: RawUTF8; auth: TECDHEAuth; kdf: TECDHEKDF;
  ef: TECDHEEF; mac: TECDHEMAC; customkey: cardinal): RawUTF8;
begin
  FormatUTF8('a=%',[ord(auth)],result);
  if kdf<>low(kdf) then
    result := result+';k='+TrimLeftLowerCaseShort(ToText(kdf));
  if ef<>low(ef) then
    result := result+';e='+TrimLeftLowerCaseShort(ToText(ef));
  if mac<>low(mac) then
    result := result+';m='+TrimLeftLowerCaseShort(ToText(mac));
  result := lowercase(result);
  if pki<>'' then
    result := result+';ca='+pki;
  if privkey<>'' then begin
    result := FormatUTF8('%;p=%;pw=%',[result,privkey,privpassword]);
    if privrounds<>60000 then // DEFAULT_ECCROUNDS may change
      result := FormatUTF8('%;pr=%',[result,privrounds]);
  end;
  result := TSynPersistentWithPassword.ComputePassword(result,customkey);
end;

const
  ED: array[boolean] of string[7] = ('Decrypt','Encrypt');

procedure TECDHEProtocol.SetKey(aEncrypt: boolean);
begin
  if fAES[aEncrypt]=nil then
    raise EECCException.CreateUTF8('%.% with no handshake',[self,ED[aEncrypt]]);
  fAES[aEncrypt].IV := fkM[aEncrypt].Lo; // kM is a CTR -> IV unicity
  if fAlgo.mac=macDuringEF then
    if not fAES[aEncrypt].MACSetNonce(fkM[aEncrypt].b) then
      raise EECCException.CreateUTF8('%.%: macDuringEF not available in %/%',
        [self,ED[aEncrypt],ToText(fAlgo.ef)^,fAES[aEncrypt]]);
end;

procedure TECDHEProtocol.ComputeMAC(aEncrypt: boolean;
  aEncrypted: pointer; aLen: integer; out aMAC: THash256Rec);
var i,c: cardinal;
begin
  case fAlgo.mac of
    macDuringEF:
      if not fAES[aEncrypt].MACGetLast(aMac.b) then // computed during EF process
        raise EECCException.CreateUTF8('%.%: macDuringEF not available in %/%',
          [self,ED[aEncrypt],ToText(fAlgo.ef)^,fAES[aEncrypt]]);
    macHmacCrc256c:
      HMAC_CRC256C(@fkM[aEncrypt],aEncrypted,sizeof(THash256),aLen,aMAC.b);
    macHmacSha256:
      HMAC_SHA256(@fkM[aEncrypt],aEncrypted,sizeof(THash256),aLen,aMAC.b);
    macHmacCrc32c: begin
      c := HMAC_CRC32C(@fkM[aEncrypt],aEncrypted,sizeof(THash256),aLen);
      for i := 0 to 7 do
        aMac.c[i] := c; // naive 256-bit diffusion
    end;
    macXxHash32: begin
      c := xxHash32(fkM[aEncrypt].i0,aEncrypted,aLen);
      for i := 0 to 7 do
        aMac.c[i] := c; // naive 256-bit diffusion
    end;
    macNone:
      crc256c(@fkM[aEncrypt],sizeof(THash256),aMAC.b); // replay attack only
    else
      raise EECCException.CreateUTF8('%.%: ComputeMAC %?',
        [self,ED[aEncrypt],ToText(fAlgo.mac)^]);
  end;
  with fkM[aEncrypt] do
    for i := 0 to 3 do begin
      inc(q[i]); // sequence number against replay attacks
      if q[i]<>0 then
        break;
    end;
end;

procedure TECDHEProtocol.Encrypt(const aPlain: RawByteString;
  out aEncrypted: RawByteString);
var len: integer;
begin
  fSafe.Lock;
  try
    SetKey(true);
    len := fAES[true].EncryptPKCS7Length(length(aPlain),false);
    SetString(aEncrypted,nil,len+sizeof(THash256));
    fAES[true].EncryptPKCS7Buffer(Pointer(aPlain),pointer(aEncrypted),
      length(aPlain),len,false);
    ComputeMac(true,pointer(aEncrypted),len,PHash256Rec(@PByteArray(aEncrypted)[len])^);
  finally
    fSafe.UnLock;
  end;
end;

function TECDHEProtocol.Decrypt(const aEncrypted: RawByteString;
  out aPlain: RawByteString): TProtocolResult;
var P: PAnsiChar absolute aEncrypted;
    len, i: integer;
    mac: THash256Rec;
begin
  result := sprInvalidMAC;
  len := length(aEncrypted)-sizeof(THash256);
  if len<=0 then
    exit;
  fSafe.Lock;
  try
    SetKey(false);
    aPlain := fAES[false].DecryptPKCS7Buffer(P,len,false,false);
    if aPlain='' then begin
      with fkM[false] do
        for i := 0 to 3 do begin
          inc(q[i]); // don't compute MAC, but increase sequence
          if q[i]<>0 then
            break;
        end;
      exit;
    end;
    ComputeMac(false,P,len,mac);
    if IsEqual(mac.b,PHash256(P+len)^) then
      result := sprSuccess;
  finally
    fSafe.Unlock;
  end;
end;

function TECDHEProtocol.CheckError(const aEncrypted: RawByteString): TProtocolResult;
begin
  if fAlgo.mac<>macDuringEF then begin
    result := sprUnsupported;
    exit;
  end;
  fSafe.Lock;
  try
    SetKey(false);
    if fAES[false].MACCheckError(pointer(aEncrypted),length(aEncrypted)) then
      result := sprSuccess else
      result := sprInvalidMAC;
  finally
    fSafe.Unlock;
  end;
end;

procedure TECDHEProtocol.SharedSecret(sA,sB: PHash256);
const
  AES_CLASS: array[TECDHEEF] of TAESAbstractClass = (
  // efAesCrc, efAesCfb, efAesOfb, efAesCtr, efAesCbc
    TAESCFBCRC, TAESCFB, TAESOFB, TAESCTR, TAESCBC,
    TAESCFBCRC, TAESCFB, TAESOFB, TAESCTR, TAESCBC);
  AES_BITS: array[TECDHEEF] of integer = (
    128, 128, 128, 128, 128, 256, 256, 256, 256, 256);
var secret: THash256;
  procedure ComputeSecret(const salt: RawByteString);
  var hmac: THMAC_SHA256;
  begin
    hmac.Init(pointer(salt),length(salt));
    if fAlgo.auth<>authServer then
      hmac.Update(sA^);
    if fAlgo.auth<>authClient then
      hmac.Update(sB^);
    hmac.Update(fRndA);
    hmac.Update(fRndB);
    hmac.Done(secret);
  end;
begin
  if fAES[false]<>nil then
    raise EECCException.CreateUTF8('%.SharedSecret already called',[self]);
  if fAlgo.kdf<>kdfHmacSha256 then
    raise EECCException.CreateUTF8('%.SharedSecret %?',[self,ToText(fAlgo.kdf)^]);
  try
    ComputeSecret(fEFSalt);
    fAES[false] := AES_CLASS[fAlgo.ef].Create(secret,AES_BITS[fAlgo.ef]);
    fAES[true] := fAES[false].CloneEncryptDecrypt;
    ComputeSecret(fMACSalt);
    fkM[false].b := secret; // first 128-bit also used as AES IV
    fkM[true].b := secret;
  finally
    FillZero(secret);
  end;
end;

function TECDHEProtocol.Verify(frame: PByteArray; len: integer;
  const QC: TECCCertificateContent; out res: TProtocolResult): boolean;
var hash: TSHA256Digest;
    sha: TSHA256;
begin
  result := false;
  res := sprInvalidCertificate;
  if fPKI<>nil then begin
    fCertificateValidity := fPKI.IsValid(QC);
    if not (fCertificateValidity in ECC_VALIDSIGN) then
      exit;
  end else
    if not ECCCheck(QC) then
      exit;
  dec(len,sizeof(TECCSignature)); // Sign at the latest position
  sha.Full(frame,len,hash);
  res := sprInvalidSignature;
  if not ecdsa_verify(QC.Signed.PublicKey,hash,PECCSignature(@frame[len])^) then
    exit;
  res := sprSuccess;
  result := true;
end;

procedure TECDHEProtocol.Sign(frame: PByteArray; len: integer;
  out QC: TECCCertificateContent);
var hash: TSHA256Digest;
    sha: TSHA256;
begin
  QC := fPrivate.fContent;
  dec(len,sizeof(TECCSignature)); // Sign at the latest position
  sha.Full(frame,len,hash);
  if not ecdsa_sign(fPrivate.fPrivateKey,hash,PECCSignature(@frame[len])^) then
    raise EECCException.CreateUTF8('%.Sign: ecdsa_sign?',[self]);
end;

function TECDHEProtocol.Clone: IProtocol;
begin
  result := TECDHEProtocolClass(ClassType).CreateFrom(self);
end;


{ TECDHEProtocolClient }

constructor TECDHEProtocolClient.Create(aAuth: TECDHEAuth;
  aPKI: TECCCertificateChain; aPrivate: TECCCertificateSecret);
begin
  if (aAuth<>authServer) and not aPrivate.CheckCRC then
    raise EECCException.CreateUTF8('%.Create: need valid Private Key for %',
      [self,ToText(aAuth)^]) else
  inherited;
end;

procedure TECDHEProtocolClient.ComputeHandshake(out aClient: TECDHEFrameClient);
begin
  if fAES[false]<>nil then
    raise EECCException.CreateUTF8('%.ComputeHandshake already called',[self]);
  FillCharFast(aClient,sizeof(aClient),0);
  aClient.algo := fAlgo;
  TAESPRNG.Main.FillRandom(fRndA);
  aClient.RndA := fRndA;
  if fAlgo.auth<>authClient then
    if not ecc_make_key(aClient.QE,fdE) then
      raise EECCException.CreateUTF8('%.ComputeHandshake: ecc_make_key?',[self]);
  if fAlgo.auth<>authServer then
    Sign(@aClient,sizeof(aClient),aClient.QCA);
end;

function TECDHEProtocolClient.ValidateHandshake(const aServer: TECDHEFrameServer): TProtocolResult;
var sA,sB: THash256;
begin
  result := sprUnexpectedAlgorithm;
  if cardinal(aServer.algo)<>cardinal(fAlgo) then
    exit;
  result := sprBadRequest;
  if IsZero(fRndA) or not IsEqual(aServer.RndA,fRndA) or
     IsZero(aServer.RndB) or IsEqual(aServer.RndA,aServer.RndB) then
    exit;
  fRndB := aServer.RndB;
  if fAlgo.auth<>authClient then
    if not Verify(@aServer,sizeof(aServer),aServer.QCB,result) then
      exit;
  try
    result := sprInvalidEphemeralKey;
    if fAlgo.auth<>authServer then
      if not ecdh_shared_secret(aServer.QF,fPrivate.fPrivateKey,sA) then
        exit;
    result := sprInvalidPublicKey;
    if fAlgo.auth<>authClient then
      if not ecdh_shared_secret(aServer.QCB.Signed.PublicKey,fdE,sB) then
        exit;
    SharedSecret(@sA,@sB);
  finally
    FillZero(sA);
    FillZero(sB);
    FillZero(fdE);
  end;
  result := sprSuccess;
end;

function TECDHEProtocolClient.ProcessHandshake(const MsgIn: RawUTF8;
  out MsgOut: RawUTF8): TProtocolResult;
var out1: TECDHEFrameClient;
    in2: TECDHEFrameServer;
begin
  if MsgIn='' then begin
    ComputeHandshake(out1);
    MsgOut := BinToBase64(@out1,SizeOf(out1));
    result := sprSuccess;
  end else
    if Base64ToBin(Pointer(MsgIn),@in2,length(MsgIn),sizeof(in2),false) then
      result := ValidateHandshake(in2) else
      result := sprBadRequest;
end;


{ TECDHEProtocolServer }

constructor TECDHEProtocolServer.Create(aAuth: TECDHEAuth;
  aPKI: TECCCertificateChain; aPrivate: TECCCertificateSecret);
begin
  if (aAuth<>authClient) and not aPrivate.CheckCRC then
    raise EECCException.CreateUTF8('%.Create: need valid Private Key for %',
      [self,ToText(aAuth)^]);
  inherited;
  include(fAuthorized,aAuth); // conservative default
end;

constructor TECDHEProtocolServer.CreateFrom(aAnother: TECDHEProtocol);
begin
  inherited CreateFrom(aAnother);
  fAuthorized := (aAnother as TECDHEProtocolServer).fAuthorized;
end;

function TECDHEProtocolServer.ComputeHandshake(const aClient: TECDHEFrameClient;
  out aServer: TECDHEFrameServer): TProtocolResult;
var dF: TECCPrivateKey;
    sA,sB: THash256;
begin
  result := sprUnexpectedAlgorithm;
  if cardinal(aClient.algo)<>cardinal(fAlgo) then begin
    if not (aClient.algo.auth in fAuthorized) or
       (aClient.algo.kdf<>fAlgo.kdf) or (aClient.algo.ef<>fAlgo.ef) or
       (aClient.algo.mac<>fAlgo.mac) then
      exit;
    if (aClient.algo.auth<>authClient) and not fPrivate.CheckCRC then
      exit;
    fAlgo.auth := aClient.algo.auth; // client forced another mode
  end;
  result := sprBadRequest;
  if IsZero(aClient.RndA) then
    exit;
  fRndA := aClient.RndA;
  if fAlgo.auth<>authServer then
    if not Verify(@aClient,sizeof(aClient),aClient.QCA,result) then
      exit;
  FillCharFast(aServer,sizeof(aServer),0);
  aServer.algo := fAlgo;
  aServer.RndA := fRndA;
  TAESPRNG.Main.FillRandom(fRndB);
  aServer.RndB := fRndB;
  if fAlgo.auth<>authServer then
    if not ecc_make_key(aServer.QF,dF) then
      raise EECCException.CreateUTF8('%.ComputeHandshake: ecc_make_key?',[self]);
  try
    result := sprInvalidPublicKey;
    if fAlgo.auth<>authServer then
      if not ecdh_shared_secret(aClient.QCA.Signed.PublicKey,dF,sA) then
        exit;
    result := sprInvalidEphemeralKey;
    if fAlgo.auth<>authClient then
      if not ecdh_shared_secret(aClient.QE,fPrivate.fPrivateKey,sB) then
        exit;
    SharedSecret(@sA,@sB);
  finally
    FillZero(sA);
    FillZero(sB);
    FillZero(dF);
  end;
  if fAlgo.auth<>authClient then
    Sign(@aServer,sizeof(aServer),aServer.QCB);
  result := sprSuccess;
end;

function TECDHEProtocolServer.ProcessHandshake(const MsgIn: RawUTF8;
  out MsgOut: RawUTF8): TProtocolResult;
var in1: TECDHEFrameClient;
    out1: TECDHEFrameServer;
begin
  if Base64ToBin(Pointer(MsgIn),@in1,length(MsgIn),sizeof(in1),false) then begin
    result := ComputeHandshake(in1,out1);
    MsgOut := BinToBase64(@out1,SizeOf(out1));
  end else
    result := sprBadRequest;
end;


{$ifndef NOVARIANTS}

{ TJWTES256 }

constructor TJWTES256.Create(aCertificate: TECCCertificate;
  aClaims: TJWTClaims; const aAudience: array of RawUTF8;
  aExpirationMinutes: integer; aIDIdentifier: TSynUniqueIdentifierProcess;
  aIDObfuscationKey: RawUTF8);
begin
  if not aCertificate.CheckCRC then
    raise EJWTException.CreateUTF8('%.Create(aCertificate?)',[self]);
  inherited Create('ES256',aClaims,aAudience,aExpirationMinutes,
    aIDIdentifier,aIDObfuscationKey);
  fCertificate := aCertificate;
end;

destructor TJWTES256.Destroy;
begin
  if fOwnCertificate then
    fCertificate.Free;
  inherited;
end;

procedure TJWTES256.CheckSignature(const headpayload: RawUTF8; const signature: RawByteString;
  var JWT: TJWTContent);
var sha: TSHA256;
    hash: TSHA256Digest;
begin
  JWT.result := jwtInvalidSignature;
  if length(signature)<>sizeof(TECCSignature) then
    exit;
  sha.Full(pointer(headpayload),length(headpayload),hash);
  if ecdsa_verify(fCertificate.fContent.Signed.PublicKey,hash,PECCSignature(signature)^) then
    JWT.result := jwtValid;
end;

function TJWTES256.ComputeSignature(const headpayload: RawUTF8): RawUTF8;
var sha: TSHA256;
    hash: TSHA256Digest;
    sign: TECCSignature;
begin
  if not fCertificate.InheritsFrom(TECCCertificateSecret) or
     not TECCCertificateSecret(fCertificate).HasSecret then
    raise EECCException.CreateUTF8('%.ComputeSignature expects % (%) to hold '+
      'a private key',[self,fCertificate,fCertificate.Serial]);
  sha.Full(pointer(headpayload),length(headpayload),hash);
  if not ecdsa_sign(TECCCertificateSecret(fCertificate).fPrivateKey,hash,sign) then
    raise EECCException.CreateUTF8('%.ComputeSignature: ecdsa_sign?',[self]);
  result := BinToBase64URI(@sign,sizeof(sign));
end;

{$endif NOVARIANTS}

initialization
  {$ifdef HASUINT64}
  assert(NUM_ECC_DIGITS=4);
  {$endif}
  assert(sizeof(TECCCertificateContent)=173); // on all platforms and compilers
  assert(sizeof(TECDHEFrameClient)=290);
  assert(sizeof(TECDHEFrameServer)=306);
  {$ifdef ECC_32ASM}
  pointer(@ecc_make_key) := pointer(@_ecc_make_key);
  pointer(@ecdh_shared_secret) := pointer(@_ecdh_shared_secret);
  pointer(@ecdsa_sign) := pointer(@_ecdsa_sign);
  pointer(@ecdsa_verify) := pointer(@_ecdsa_verify);
  {$endif ECC_32ASM}
end.

