-- Implementation of TMSR's OAEP with Keccak as hash function
-- NB: this uses Eulora's protocol constants (esp. RSA key length) and types.
--
-- S.MG, 2018

with Keccak;    -- Keccak is used as hash function
with Raw_Types; -- Eulora's protocol raw types and constant values

with Interfaces; use Interfaces; -- for Unsigned_8 type and bit-level ops

package OAEP is
  pragma Pure( OAEP ); -- stateless, no side effects -> can cache calls

  -- constants for OAEP 
  OAEP_LENGTH_OCTETS : constant := Raw_Types.RSA_KEY_OCTETS;
  OAEP_LENGTH_BITS   : constant := OAEP_LENGTH_OCTETS * 8;
  OAEP_HALF_OCTETS   : constant := OAEP_LENGTH_OCTETS / 2;
  TMSR_STR           : constant String := Raw_Types.OAEP_R_STR;
    -- "TMSR-RSA" as unsigned_8 values:
  MAX_LEN_MSG        : constant := Raw_Types.OAEP_MAX_LEN;

  -- subtypes for OAEP encrypt/decrypt
  subtype OAEP_Block is Raw_Types.Octets( 1 .. OAEP_LENGTH_OCTETS );
  subtype OAEP_HALF is Raw_Types.Octets( 1 .. OAEP_HALF_OCTETS );

  -- padding & formatting of maximum MAX_LEN_MSG octets of the given input
  -- uses TMSR's OAEP schema:
  -- 1.format M00 as: [random octet][sz1][sz2]"TMSR-RSA"[random]*Message
  --    where sz1 and sz2 store the length of the message in bits
  --    the random octets before message are padding to make OAEP_LENGTH_OCTETS
  -- 2. R = OAEP_HALF_OCTETS random bits
  -- 3. X = M00 xor hash(R)
  -- 4. Y = R xor hash(X)
  -- 5. Result is X || Y 
  -- NB: the Entropy parameter should be random octets from which this method 
  -- will use as many as required for the OAEP encryption of given Msg
  -- NB: at MOST MAX_LEN_MSG octets of Msg! (Msg at most MAX_LEN_MSG*8 bits!)
  procedure OAEP_Encrypt( Msg      : in Raw_Types.Octets;
                          Entropy  : in OAEP_Block;
                          Output   : out OAEP_Block);


  -- This is the opposite of OAEP_Encrypt above.
  -- @param Encr - an OAEP block previously obtained from OAEP_Encrypt
  -- @param Len - this will hold the length of the obtained message (in bits!)
  -- @param Output - the first Len octets of this are the recovered message
  -- @param Success - set to TRUE if message was recovered, false otherwise
  -- NB: when Success is FALSE, both Len and Output have undefined values
  procedure OAEP_Decrypt( Encr    : in OAEP_Block;
                          Len     : out Natural;
                          Output  : out OAEP_HALF;
                          Success : out Boolean);

private
  -- gnat-specific methods for bit-level operations
	function Shift_Right( Value  : Unsigned_8; 
                        Amount : Natural ) 
                        return Unsigned_8;
  pragma Import(Intrinsic, Shift_Right); 

	function Shift_Left( Value  : Unsigned_8; 
                        Amount : Natural ) 
                        return Unsigned_8;
  pragma Import(Intrinsic, Shift_Left); 

  -- helper method: xor 2 half-oaep blocks
  function XOR_Octets(A : in OAEP_HALF; 
                      B : in OAEP_HALF)
                     return OAEP_HALF;

  -- conversions between bitstream and string
  -- NB: caller has to ensure correct size of output parameter! no checks here.
  procedure ToOctets   ( B : in Keccak.Bitstream; 
                         O : out Raw_Types.Octets );

  procedure ToBitstream( O : in Raw_Types.Octets; 
                         B : out Keccak.Bitstream );

  -- wrapper for Sponge to use Octets for input/output
  procedure HashKeccak( Input     : in Raw_Types.Octets;
                        Output    : out Raw_Types.Octets;
                        Block_Len : in Keccak.Keccak_Rate := 
                                       Keccak.Default_Bitrate);

end OAEP;
