//   Course Number: COSC 645 Applied Cryptography
//    Program Name: ShamirEncoder.
//         Authors: Brian Hoffman
//                  Derek Waby
//                  Kypros Ioannou
//                  Harsha V. Reddy
//     Description:  This program generates secret shares from an entered number 
//                   based on Shamir's algorithm. The program begins by presenting 
//                   the user with a GUI divided into left and right halves. On 
//                   the left side is a data entry panel where the user can supply 
//                   the secret key (a large number), a prime number (q) for
//                   performing the necessary modulus arithemtic, the number of
//                   shares desired, and the minimum number of shares required to
//                   recreate the secret. The entry panel also includes a button
//                   that allows the user to read the key and prime number from a
//                   file. The  file should have the key on the first line and the
//                   prime # on the second. The right half of the GUI holds an
//                   output panel that displays the prime number and the shares
//                   produced. Below the output area is a button that lets the
//                   user write the shares to a directory with one share per file.
//                   This simplifies share distribution because the user can 
//                   encrypteach file and send it to the appropriate party. The
//                   prime number is also written to its own file so that it can
//                   be sent to all participants. 
//
//           Update: The encoder has been altered so that if a checkbox is enabled
//                   on the data entry panel, the program will generate a file 
//                   that enables shares to be evaluated using Feldman's method. 
//                   This file contains a lare prime number p such that p-1 is
//                   multiple of q, a value g, and g raiseed to the power of each 
//                   of the Shamir polynomial coeffients. This last step is used 
//                   to hide the coefficients useing the know difficulty of solving
//                   the discreet log problem.
//
//                   The program has also been alterd so that the user no longer 
//                   needs to enter a prime number. If one is not supplied, the 
//                   program will generate one larger than the key and write it 
//                   to the output area and the file.


// Java Core Packages
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.Random;
import java.math.BigInteger;

// Jave Extension Packages 
import javax.swing.*;
import javax.swing.border.*;

public class ShamirEncoder extends JFrame
{ 
    // private class variables
    private Container container;
    private int numShares;
    private int numRebuild;
    private BigInteger primeNum;
    private BigInteger key;
    private BigInteger [] sCoeff;
    private File outputDir = new File(".");
    private Random rand = new Random();
    private boolean doValidate = false;
    
    // GUI Components
    private GriddedPanel inputPanel;
    private JLabel keyLabel;
    private JTextArea keyArea;
    private JLabel primeLabel;
    private JTextArea primeArea;
    private JLabel numSharesLabel;
    private JTextField numSharesField;
    private JLabel numRebuildLabel;
    private JTextField numRebuildField;
    private JCheckBox validateBox;
    private JButton submitButton;
    private JButton resetButton;
    private JButton readButton;
    private JPanel outputPanel;
    private JTextArea outputArea;
    private JButton outDirButton;
    
    // ------------------------------------------------------------------------
    // constructor - sets up the GUI
    //-------------------------------------------------------------------------
    public ShamirEncoder()
    {
        // title the window 
        super("Shamir Share Encoding Program");
        
        // get content pane and set layout
        container = getContentPane();
        container.setLayout(new BorderLayout());
        
        // construct the input panel components
        inputPanel = new GriddedPanel();
        inputPanel.setBorder(BorderFactory.createTitledBorder(
                             BorderFactory.createEtchedBorder(), "Input Area",
                             TitledBorder.CENTER, TitledBorder.TOP ));
        keyLabel = new JLabel("Enter key to be shared: ");        
        keyArea = new JTextArea(5, 20);
        keyArea.setLineWrap(true);
        primeLabel = new JLabel("Enter a prime number (Optional): ");
        primeArea = new JTextArea(5,20);
        primeArea.setLineWrap(true);
        numSharesLabel = new JLabel("Enter desired number of shares: ");
        numSharesField = new JTextField(10);
        numRebuildLabel = new JLabel("Enter number of shares needed to rebuild key:");
        numRebuildField = new JTextField(10);
        validateBox = new JCheckBox("Print Feldman's Validation File");
        validateBox.addItemListener(new ValidateBoxHandler());
        submitButton = new JButton("Submit");
        submitButton.addActionListener(new SubmitButtonHandler());
        resetButton = new JButton("Reset"); 
        resetButton.addActionListener(new ResetButtonHandler());
        readButton = new JButton("Read from File");
        readButton.addActionListener(new ReadButtonHandler(this));
        
        // add input components to the panel and then add it to the frame
        inputPanel.addComponent(keyLabel, 1, 1, 3, 1);
        inputPanel.addFilledComponent(new JScrollPane(keyArea), 2, 1, 3, 2,
                                      GridBagConstraints.BOTH);
        inputPanel.addComponent(primeLabel, 4, 1, 3, 1);
        inputPanel.addFilledComponent(new JScrollPane(primeArea), 5, 1, 3, 2,
                                      GridBagConstraints.BOTH);
        inputPanel.addComponent(numSharesLabel, 7, 1, 3, 1);
        inputPanel.addComponent(numSharesField, 8, 1, 3, 1);
        inputPanel.addComponent(numRebuildLabel, 9, 1, 3, 1);
        inputPanel.addComponent(numRebuildField, 10, 1, 3, 1);
        inputPanel.addComponent(validateBox, 11, 1, 3, 1);
        inputPanel.addComponent(submitButton, 12, 1, 1, 1);
        inputPanel.addComponent(resetButton, 12, 2, 1, 1);
        inputPanel.addComponent(readButton, 12, 3, 1, 1);
        container.add(inputPanel, BorderLayout.WEST);
        
        // create output panel and add it to the frame
        outputPanel = new JPanel();
        outputPanel.setLayout(new BorderLayout());
        outputPanel.setBorder(BorderFactory.createTitledBorder(
                              BorderFactory.createEtchedBorder(), "Output Area",
                              TitledBorder.CENTER, TitledBorder.TOP ));
        outputArea = new JTextArea(20, 30);
        outputPanel.add(new JScrollPane(outputArea), BorderLayout.CENTER);
        outDirButton = new JButton("Set Output Directory");
        outDirButton.addActionListener(new OutDirButtonHandler(this));
        outputPanel.add(outDirButton, BorderLayout.SOUTH);
        container.add(outputPanel, BorderLayout.CENTER);
       
        // display the initial GUI
        pack();
        setVisible(true);   
    }
    
    // ------------------------------------------------------------------------
    // ValidateBoxHandler - changes the global doValidate variable to reflect 
    //                      the current state of the checkbox
    //-------------------------------------------------------------------------
    private class ValidateBoxHandler implements ItemListener
    {
        public void itemStateChanged(ItemEvent event)
        {   
            doValidate = !doValidate;
        }
    } 
    
    // ------------------------------------------------------------------------
    // submitButtonHandler - computes the shares based on the values entered 
    //                       into the GUI
    //-------------------------------------------------------------------------
    private class SubmitButtonHandler implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            try
            {
                // get all of the needed values from the GUI
                key = new BigInteger(keyArea.getText());
                numShares = Integer.parseInt(numSharesField.getText());
                numRebuild = Integer.parseInt(numRebuildField.getText());
                sCoeff = new BigInteger[numRebuild];
                
                // compute the prime number or use the one provided. In either 
                // case write it to the output field. 
                if(primeArea.getText().length() == 0)
                {
                    do {
                        primeNum = new BigInteger(key.bitLength()+2, 10, rand);
                    }
                    while (primeNum.compareTo(key) <= 0);
                }
                else
                {
                    primeNum = new BigInteger(primeArea.getText());
                } 
                outputArea.setText("The prime number is: \n" + primeNum +"\n");
                writePrime();
                outputArea.append("Prime number written to prime.txt in output directory\n\n");
                outputArea.append("Computed Shares \n ----------------------\n");
                
                // build the shamir polynomial by generating coefficients as 
                // random BigInteger's
                sCoeff[0] = key;
                for (int i=1; i<numRebuild; i++)
                {                    
                    sCoeff[i] = new BigInteger(key.bitLength(), rand).mod(primeNum);
                }
                
                //compute the values of each of the shares and output to
                // the output area and the later to a file 
                for (int share = 1; share <= numShares; share++)
                {
                    BigInteger value = sCoeff[0];
                    int powx = share;
                    for (int n=1; n<numRebuild; n++)
                    {
                        BigInteger term = new BigInteger(Integer.toString(powx));
                        term = term.multiply(sCoeff[n]);
                        value = value.add(term);
                        value = value.mod(primeNum);
                        powx *= share;
                    }
                    outputArea.append("(" + Integer.toString(share) + " , "
                                          + value.toString() + ") \n");      
                    writeShare(share, value);
                }
                outputArea.append("\n Shares written to share#.txt in output directory.");
                if (doValidate)
                {
                    writeValidation();
                    outputArea.append("\n Fellman\'s validation keys written to validate.txt");
                }
            }
            catch(NumberFormatException e)
            {
                JOptionPane.showMessageDialog(null,"Check your number format!",
                        "Number Format Error", JOptionPane.ERROR_MESSAGE);
                return;
            }       
        } // end actionPerformed
    } // end SubmitButtonHandler
    
    // ------------------------------------------------------------------------
    // resetButtonHandler - clears all of the input fields
    //-------------------------------------------------------------------------
    private class ResetButtonHandler implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            keyArea.setText("");
            primeArea.setText("");
            numSharesField.setText("");
            numRebuildField.setText("");
            outputArea.setText("");
        }
    } 

    // ------------------------------------------------------------------------
    // ReadButtonHandler - pops up a JFileChooser dialog that allows the user 
    //                     to indicate a file from which the key and possibly 
    //                     the prime number can be read. The key must occur on 
    //                     the first line and the prime number, if it exits, on
    //                     the second.
    //-------------------------------------------------------------------------
    private class ReadButtonHandler implements ActionListener
    {
        private JFrame parent;
        
        public ReadButtonHandler(JFrame inFrame)
        {
            parent = inFrame;
        }
        
        public void actionPerformed(ActionEvent event)
        {
            // open a file chooser dialog and get the input file
            JFileChooser chooser = new JFileChooser();
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            chooser.setCurrentDirectory(new File("."));
            if(chooser.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION)
                return;
            File fileName = chooser.getSelectedFile();  
            
            // setup a buffered reader for the file
            FileReader theFile;
            BufferedReader inFile = null;
            try
            {
                 theFile = new FileReader(fileName);
                 inFile = new BufferedReader(theFile);
            
                // read in key from the first line 
                String temp = inFile.readLine();
                if (temp == null)
                {
                    JOptionPane.showMessageDialog(null, "File is Empty", 
                                       "Empty File", JOptionPane.ERROR_MESSAGE);
                }
                else // read the prime from the second line if it exists
                {
                    keyArea.setText(temp);
                    temp = inFile.readLine();
                    if (temp != null) primeArea.setText(temp);
                }
            }
            catch(IOException ioException)
            {
                JOptionPane.showMessageDialog(null, "Error reading from file!", 
                                  "File Read Error", JOptionPane.ERROR_MESSAGE);
            }
            finally  // close the file stream
            {
                try
                { if (inFile != null) inFile.close(); }
                catch(IOException e)
                {} // do nothing                     
            }   
        } // end ActionPerformed
    } // end ReadButtonHandler

    // ------------------------------------------------------------------------
    // OutDirButtonHandler - pop up a JFileChooser dialog box that allows the 
    //                       user to set the directory where the shares will 
    //                       be written.
    //-------------------------------------------------------------------------
    private class OutDirButtonHandler implements ActionListener
    {
        private JFrame parent;
        
        public OutDirButtonHandler(JFrame inFrame)
        {
            parent = inFrame;
        }
        
        public void actionPerformed(ActionEvent event)
        {
            // open a file chooser dialog and get the output directory
            JFileChooser chooser = new JFileChooser();
            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            chooser.setCurrentDirectory(new File("."));
            if(chooser.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION)
                return;
            outputDir = chooser.getSelectedFile();
        }
    } 

    // ------------------------------------------------------------------------
    // writePrime - writes the prime number to a file
    //-------------------------------------------------------------------------
    private void writePrime()
    {
        FileWriter theFile;
        PrintWriter outFile = null;
        
        try
        {
            theFile = new FileWriter(outputDir.getPath() + "\\prime.txt");
            outFile = new PrintWriter(theFile);
            
            outFile.println(primeNum);
        }
        catch (IOException ioException)
        {
            JOptionPane.showMessageDialog(null, "Error writing to file!", 
                                  "File Read Error", JOptionPane.ERROR_MESSAGE);
            ioException.printStackTrace();
        }
        finally  // close the file stream
        {
            if (outFile != null) outFile.close();                  
        } 
    }
    
    // ------------------------------------------------------------------------
    // writeShare - writes a computed share to a file
    //-------------------------------------------------------------------------
    private void writeShare(int share, BigInteger value)
    {
        FileWriter theFile;
        PrintWriter outFile = null;
        
        try
        {
            theFile = new FileWriter(outputDir.getPath() + "\\share"+ share + ".txt");
            outFile = new PrintWriter(theFile);
            
            outFile.println(share);
            outFile.println(value);
        }
        catch (IOException ioException)
        {
            JOptionPane.showMessageDialog(null, "Error writing to file!", 
                                  "File Read Error", JOptionPane.ERROR_MESSAGE);
            ioException.printStackTrace();
        }
        finally  // close the file stream
        {
            if (outFile != null) outFile.close();                  
        } 
    }

    // ------------------------------------------------------------------------
    // writeValidation - writes out a file containing the information neccessary 
    //                   to validate a share based on Feldman's algorithm in 
    //                   which the coefficients of the Shamir polynomial are 
    //                   hidden using difficulty of solving the discrete log
    //                   problem.
    //-------------------------------------------------------------------------
    private void writeValidation()
    {
        BigInteger g;
        BigInteger largerPrime;
        
        // determine a larger prime such that (largerPrime - 1) = k * primeNum
        // according to our text, this should not take forever
        do {
            BigInteger testK = new BigInteger(10, rand);
            largerPrime = testK.multiply(primeNum);
            largerPrime = largerPrime.add(BigInteger.ONE);
        }
        while (!largerPrime.isProbablePrime(10));
        
        FileWriter theFile;
        PrintWriter outFile = null;
        try
        {
            theFile = new FileWriter(outputDir.getPath() + "\\validate.txt");
            outFile = new PrintWriter(theFile);
            
            // compute a value of g such that g^primeNum = 1 (mod largerPrime)
            do {
                g = new BigInteger(largerPrime.bitLength(), rand);
                g = g.mod(largerPrime);
               }
            while (!BigInteger.ONE.equals(g.modPow(primeNum, largerPrime))
                   || !(g.compareTo(BigInteger.ONE) > 0 )); 
                   
            outFile.println(largerPrime);
            outFile.println(g);
            for(int i=0; i<numRebuild; i++)
            {
                outFile.println(g.modPow(sCoeff[i], largerPrime));
            }
        }
        
        catch (IOException ioException)
        {
            JOptionPane.showMessageDialog(null, "Error writing validation to file!", 
                                  "File Read Error", JOptionPane.ERROR_MESSAGE);
            ioException.printStackTrace();
        }
        finally  // close the file stream
        {
            if (outFile != null) outFile.close();                  
        }
    }
    
    // ------------------------------------------------------------------------
    // main - executes application
    //------------------------------------------------------------------------- 
    public static void main (String args[])
    {
        ShamirEncoder application = new ShamirEncoder();
        application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }       
}