#!/usr/bin/python3

######################################################################
#
# pek is a command to encrypt and decrypt files using public/private keys.
# A secondary feature of pek is signing and verifying files using digital signatures.
# Written for python 3.
#
# This program has been designed to be an easy-to-install and simple-to-use tool
# for exchanging small encrypted messages via email. The encrypted output was designed to
# be cut and pasted from a terminal window into an email message. This program was designed
# to be given to someone with minimal technical skills, such that they have a reasonable
# chance of getting its dependencies installed and running. I.e. communicating with your parents/siblings.
#
# Synopsis:
#    C:\> PEK CREATE
#    Public key:  HDiIVl7zm5prDw9CjsGwFagO5iR/+d4WKljeLbWiVWK
#    Private key: UczjINLL6k318mjZOAF83XHiqHJERkb2vrUd7YLbOyl
#
#    C:\> PEK ENCRYPT HDiIVl7zm5prDw9CjsGwFagO5iR/+d4WKljeLbWiVWK C:\AUTOEXEC.BAT SECRET.PEK
#
#    C:\> TYPE SECRET.PEK
#    qAqk2x6H73km+5egvwik+vKUcMgwrSFV5WRyNA0LBwEDFJIygBdlKOOX4KIOdw
#    Khui6zxb4IzjW+smFqkjVNn2Ng8nhYes4+YK6ucTGO/DBHX6jKUVuckN9aiC+es
#    B4n0YOb53JeSzrXMa3Cw2+Dtv3CCrSTnQPu5h/9ylx1gP8/CIYi3rgTc2qygnOJ
#    csHU8/zdZKgEu5i/wr98uTQrMelQIBsSUyBVYHTSnVoBOjRwxlhxJxWQsw9cdw
#    E4J+JnRg
#
#    C:\> PEK DECRYPT UczjINLL6k318mjZOAF83XHiqHJERkb2vrUd7YLbOyl SECRET.PEK -
#    @echo off
#    SET SOUND=C:\PROGRA~1\CREATIVE\CTSND
#    SET BLASTER=A220 I5 D1 H5 P330 E620 T6
#    SET PATH=C:\Windows,C:\
#    LH C:\Windows\COMMAND\MSCDEX.EXE /D:123
#
#    C:\> PEK SIGN UczjINLL6k318mjZOAF83XHiqHJERkb2vrUd7YLbOyl C:\AUTOEXEC.BAT A.SIG
#
#    C:\> TYPE A.SIG
#    rnl+t/pN5lOE7ZsUJjdEc8utQHrhqgLzLVkksu2EXlmC8jbrRnGt7k1xw6lJYQ
#    QveDNKUx7rzYHLxTq71oj/pA
#
#    C:\> PEK VERIFY HDiIVl7zm5prDw9CjsGwFagO5iR/+d4WKljeLbWiVWK C:\AUTOEXEC.BAT A.SIG
#    GOOD
#
#    C:\> PEK
#    1) Create Keys
#    2) Encrypt file
#    3) Decrypt file
#    4) Create Signature
#    5) Verify Signature
#    6) File Operations
#    7) Help
#    8) Quit
#    Choose 1-8? 8
#
#    C:\> 
#
# Encryption / Decryption:
#
# The private key is a random 256-bit value (represented by the variable 'd').
# The public key is the secp256k1 generator point multiplied by the private key (represented by the variable 'Q').
#
# The shared key (see the 'sk' variable in ENCRYPT() and DECRYPT()) is derived
# using ECDH (Elliptic Curve Diffie-Hellman) Key Exchange. An ephemeral private/public key pair is
# produced (represented by 'e' and 'E'). The public ephemeral key is included in the encrypted output so
# that that the decryption algorithm can derive the shared key 'sk'.
#
# SHA256 is applied to the elliptic curve point to get the 256-bit key 'sk' used for
# the AES encryption and decryption. (See the function HASH_POINT())
#
# Once the shared key is obtained the plaintext is encrypted with it using AES-128 (Advanced
# Encryption Standard). Cipher block chaining (CBC) is employed. The initialization vector is the shared key 'sk'.
#
# This AES implementation operates on 128-bit blocks (it does use a 256-bit key).
# The lower 128-bits and upper 128-bits are encrypted seperatly and then combined
# (see AES_ENCRYPT() and AES_DECRYPT()).
#
# The public and private keys are represented by a 43-character BASE64 encoded string.
# All 258-bits of this 43-character string are used. The first 256-bits are the private
# key or public key. To encode a public key one more bit is needed to indicate
# if the y coordinate is even/odd. To capture this information the next and final two
# bits are used as follows:
#   00  Invalid.
#   01  This is a private key.
#   10  This is a public key, the y-coordinate is even.
#   11  This is a public key, the y-coordinate is odd.
#
# The encrypted file format is a stream of BASE64 characters with allowance
# for whitespace appearing anywhere. The first 33-bytes are the the public ephemeral key.
# The remaining data is the encrypted form of the plaintext.
#
# Signing / Verification:
#
# The ECDSA (Elliptic Curve Digital Signature Algorithm) is used.
# The 256-bit hash code is calculated using SHA256.
# See the SIGN() and VERIFY() functions. The hash code is represented by the variable 'e'.
# The signature file format consists of 512-bits of BASE64 characters representing the tuple (r, s).
#
# This program contains code from these other projects:
#   Elliptic Curve   Jimmy Song, https://github.com/jimmysong/programmingbitcoin.git
#   AES              Brandon Sterne, https://gist.github.com/raullenchai/2920069
#   SHA256           Thomas Dixon, https://github.com/thomdixon/pysha2
#
# Dependencies:
#   os.urandom
#   os.path.isfile
#   os.path.exists
#   os.getcwd
#   os.chdir
#   os.listdir
#   sys.stdin
#   sys.stdout
#   sys.stderr
#   sys.argv
#   io.StringIO
#   io.BytesIO
#   copy.copy
#   copy.deepcopy
#   struct.unpack
#   struct.pack
#   input(), open(), close(), read(), write()
#
# The code is organized as follows:
#
#   Section 1: Elliptic Curve
#   Section 2: AES - Advanced Encryption Standard
#   Section 3: SHA256 - Secure Hash Algorithm
#   Section 4: BASE64 Reading & Writing
#   Section 5: CREATE, ENCRYPT, DECRYPT, SIGN, VERIFY
#   Section 6: Command Line Processing
#   Section 7: Interactive Menu
#   Section 8: MAIN()
#
######################################################################

import sys
import os
import io
import copy
import struct

######################################################################
#
# Section 1: Elliptic Curve
#
######################################################################

#
# This code is from the Programming Bitcoin book by Jimmy Song
# https://github.com/jimmysong/programmingbitcoin.git
#

class FieldElement:

    def __init__(self, num, prime):
        if num >= prime or num < 0:
            error = 'Num {} not in field range 0 to {}'.format(
                num, prime - 1)
            raise ValueError(error)
        self.num = num
        self.prime = prime

    def __repr__(self):
        return 'FieldElement_{}({})'.format(self.prime, self.num)

    def __eq__(self, other):
        if other is None:
            return False
        return self.num == other.num and self.prime == other.prime

    def __ne__(self, other):
        # this should be the inverse of the == operator
        return not (self == other)

    def __add__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot add two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        num = (self.num + other.num) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)

    def __sub__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot subtract two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        num = (self.num - other.num) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)

    def __mul__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot multiply two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        num = (self.num * other.num) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)

    def __pow__(self, exponent):
        n = exponent % (self.prime - 1)
        num = pow(self.num, n, self.prime)
        return self.__class__(num, self.prime)

    def __truediv__(self, other):
        if self.prime != other.prime:
            raise TypeError('Cannot divide two numbers in different Fields')
        # self.num and other.num are the actual values
        # self.prime is what we need to mod against
        # use fermat's little theorem:
        # self.num**(p-1) % p == 1
        # this means:
        # 1/n == pow(n, p-2, p)
        num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime
        # We return an element of the same class
        return self.__class__(num, self.prime)

    def __rmul__(self, coefficient):
        num = (self.num * coefficient) % self.prime
        return self.__class__(num=num, prime=self.prime)


class Point:

    def __init__(self, x, y, a, b):
        self.a = a
        self.b = b
        self.x = x
        self.y = y
        # x being None and y being None represents the point at infinity
        # Check for that here since the equation below won't make sense
        # with None values for both.
        if self.x is None and self.y is None:
            return
        # make sure that the elliptic curve equation is satisfied
        # y**2 == x**3 + a*x + b
        if self.y**2 != self.x**3 + a * x + b:
            # if not, throw a ValueError
            raise ValueError('({}, {}) is not on the curve'.format(x, y))

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y \
            and self.a == other.a and self.b == other.b

    def __ne__(self, other):
        # this should be the inverse of the == operator
        return not (self == other)

    def __repr__(self):
        if self.x is None:
            return 'Point(infinity)'
        elif isinstance(self.x, FieldElement):
            return 'Point({},{})_{}_{} FieldElement({})'.format(
                self.x.num, self.y.num, self.a.num, self.b.num, self.x.prime)
        else:
            return 'Point({},{})_{}_{}'.format(self.x, self.y, self.a, self.b)

    def __add__(self, other):
        if self.a != other.a or self.b != other.b:
            raise TypeError('Points {}, {} are not on the same curve'.format(self, other))
        # Case 0.0: self is the point at infinity, return other
        if self.x is None:
            return other
        # Case 0.1: other is the point at infinity, return self
        if other.x is None:
            return self

        # Case 1: self.x == other.x, self.y != other.y
        # Result is point at infinity
        if self.x == other.x and self.y != other.y:
            return self.__class__(None, None, self.a, self.b)

        # Case 2: self.x ≠ other.x
        # Formula (x3,y3)==(x1,y1)+(x2,y2)
        # s=(y2-y1)/(x2-x1)
        # x3=s**2-x1-x2
        # y3=s*(x1-x3)-y1
        if self.x != other.x:
            s = (other.y - self.y) / (other.x - self.x)
            x = s**2 - self.x - other.x
            y = s * (self.x - x) - self.y
            return self.__class__(x, y, self.a, self.b)

        # Case 4: if we are tangent to the vertical line,
        # we return the point at infinity
        # note instead of figuring out what 0 is for each type
        # we just use 0 * self.x
        if self == other and self.y == 0 * self.x:
            return self.__class__(None, None, self.a, self.b)

        # Case 3: self == other
        # Formula (x3,y3)=(x1,y1)+(x1,y1)
        # s=(3*x1**2+a)/(2*y1)
        # x3=s**2-2*x1
        # y3=s*(x1-x3)-y1
        if self == other:
            s = (3 * self.x**2 + self.a) / (2 * self.y)
            x = s**2 - 2 * self.x
            y = s * (self.x - x) - self.y
            return self.__class__(x, y, self.a, self.b)

    def __rmul__(self, coefficient):
        coef = coefficient
        current = self
        result = self.__class__(None, None, self.a, self.b)
        while coef:
            if coef & 1:
                result += current
            current += current
            coef >>= 1
        return result

A = 0
B = 7
P = 2**256 - 2**32 - 977
N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

class S256Field(FieldElement):

    def __init__(self, num, prime=None):
        super().__init__(num=num, prime=P)

    def __repr__(self):
        return '{:x}'.format(self.num).zfill(64)

    def sqrt(self):
        return self**((P + 1) // 4)

class S256Point(Point):

    def __init__(self, x, y, a=None, b=None):
        a, b = S256Field(A), S256Field(B)
        if type(x) == int:
            super().__init__(x=S256Field(x), y=S256Field(y), a=a, b=b)
        else:
            super().__init__(x=x, y=y, a=a, b=b)

    def __repr__(self):
        if self.x is None:
            return 'S256Point(infinity)'
        else:
            return 'S256Point({}, {})'.format(self.x, self.y)

    def __rmul__(self, coefficient):
        coef = coefficient % N
        return super().__rmul__(coef)

    def verify(self, z, sig):
        # By Fermat's Little Theorem, 1/s = pow(s, N-2, N)
        s_inv = pow(sig.s, N - 2, N)
        # u = z / s
        u = z * s_inv % N
        # v = r / s
        v = sig.r * s_inv % N
        # u*G + v*P should have as the x coordinate, r
        total = u * G + v * self
        return total.x.num == sig.r

    def sec(self, compressed=True):
        '''returns the binary version of the SEC format'''
        if compressed:
            if self.y.num % 2 == 0:
                return b'\x02' + self.x.num.to_bytes(32, 'big')
            else:
                return b'\x03' + self.x.num.to_bytes(32, 'big')
        else:
            return b'\x04' + self.x.num.to_bytes(32, 'big') + \
                self.y.num.to_bytes(32, 'big')

G = S256Point(
    0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
    0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 )

######################################################################
#
# Section 2: AES - Advanced Encryption Standard
#
######################################################################

#
# Copyright (c) 2007 Brandon Sterne
# Licensed under the MIT license.
# http://brandon.sternefamily.net/files/mit-license.txt
# Python AES implementation

# The actual Rijndael specification includes variable block size, but
# AES uses a fixed block size of 16 bytes (128 bits)

# Additionally, AES allows for a variable key size, though this implementation
# of AES uses only 256-bit cipher keys (AES-256)

sbox = [
        0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
        0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
        0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
        0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
        0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
        0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
        0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
        0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
        0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
        0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
        0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
        0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
        0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
        0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
        0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
        0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
        ]

sboxInv = [
        0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
        0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
        0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
        0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
        0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
        0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
        0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
        0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
        0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
        0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
        0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
        0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
        0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
        0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
        0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
        0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
        ]

rcon = [
        0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
        0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
        0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
        0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
        0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
        0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
        0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
        0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
        0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
        0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
        0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
        0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
        0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
        0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
        0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
        0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb
        ]

# returns a copy of the word shifted n bytes (chars)
# positive values for n shift bytes left, negative values shift right
def rotate(word, n):
    return word[n:]+word[0:n]

# iterate over each "virtual" row in the state table and shift the bytes
# to the LEFT by the appropriate offset
def shiftRows(state):
    for i in range(4):
        state[i*4:i*4+4] = rotate(state[i*4:i*4+4],i)

# iterate over each "virtual" row in the state table and shift the bytes
# to the RIGHT by the appropriate offset
def shiftRowsInv(state):
    for i in range(4):
        state[i*4:i*4+4] = rotate(state[i*4:i*4+4],-i)

# takes 4-byte word and iteration number
def keyScheduleCore(word, i):
    # rotate word 1 byte to the left
    word = rotate(word, 1)
    newWord = []
    # apply sbox substitution on all bytes of word
    for byte in word:
        newWord.append(sbox[byte])
    # XOR the output of the rcon[i] transformation with the first part of the word
    newWord[0] = newWord[0]^rcon[i]
    return newWord

# expand 256 bit cipher key into 240 byte key from which
# each round key is derived
def expandKey(cipherKey):
    cipherKeySize = len(cipherKey)
    assert cipherKeySize == 32
    # container for expanded key
    expandedKey = []
    currentSize = 0
    rconIter = 1
    # temporary list to store 4 bytes at a time
    t = [0,0,0,0]

    # copy the first 32 bytes of the cipher key to the expanded key
    for i in range(cipherKeySize):
        expandedKey.append(cipherKey[i])
    currentSize += cipherKeySize

    # generate the remaining bytes until we get a total key size
    # of 240 bytes
    while currentSize < 240:
        # assign previous 4 bytes to the temporary storage t
        for i in range(4):
            t[i] = expandedKey[(currentSize - 4) + i]

        # every 32 bytes apply the core schedule to t
        if currentSize % cipherKeySize == 0:
            t = keyScheduleCore(t, rconIter)
            rconIter += 1

        # since we're using a 256-bit key -> add an extra sbox transform
        if currentSize % cipherKeySize == 16:
            for i in range(4):
                t[i] = sbox[t[i]]

        # XOR t with the 4-byte block [16,24,32] bytes before the end of the
        # current expanded key.  These 4 bytes become the next bytes in the
        # expanded key
        for i in range(4):
            expandedKey.append(((expandedKey[currentSize - cipherKeySize]) ^ (t[i])))
            currentSize += 1
            
    return expandedKey

# do sbox transform on each of the values in the state table
def subBytes(state):
    for i in range(len(state)):
        #print "state[i]:", state[i]
        #print "sbox[state[i]]:", sbox[state[i]]
        state[i] = sbox[state[i]]

# inverse sbox transform on each byte in state table
def subBytesInv(state):
    for i in range(len(state)):
        state[i] = sboxInv[state[i]]

# XOR each byte of the roundKey with the state table
def addRoundKey(state, roundKey):
    for i in range(len(state)):
        #print i
        #print "old state value:", state[i]
        #print "new state value:", state[i] ^ roundKey[i]
        state[i] = state[i] ^ roundKey[i]

# Galois Multiplication
def galoisMult(a, b):
    p = 0
    hiBitSet = 0
    for i in range(8):
        if b & 1 == 1:
            p ^= a
        hiBitSet = a & 0x80
        a <<= 1
        if hiBitSet == 0x80:
            a ^= 0x1b
        b >>= 1
    return p % 256

# mixColumn takes a column and does stuff
def mixColumn(column):
    temp = copy.copy(column)
    column[0] = galoisMult(temp[0],2) ^ galoisMult(temp[3],1) ^ \
                galoisMult(temp[2],1) ^ galoisMult(temp[1],3)
    column[1] = galoisMult(temp[1],2) ^ galoisMult(temp[0],1) ^ \
                galoisMult(temp[3],1) ^ galoisMult(temp[2],3)
    column[2] = galoisMult(temp[2],2) ^ galoisMult(temp[1],1) ^ \
                galoisMult(temp[0],1) ^ galoisMult(temp[3],3)
    column[3] = galoisMult(temp[3],2) ^ galoisMult(temp[2],1) ^ \
                galoisMult(temp[1],1) ^ galoisMult(temp[0],3)

# mixColumnInv does stuff too
def mixColumnInv(column):
    temp = copy.copy(column)
    column[0] = galoisMult(temp[0],14) ^ galoisMult(temp[3],9) ^ \
                galoisMult(temp[2],13) ^ galoisMult(temp[1],11)
    column[1] = galoisMult(temp[1],14) ^ galoisMult(temp[0],9) ^ \
                galoisMult(temp[3],13) ^ galoisMult(temp[2],11)
    column[2] = galoisMult(temp[2],14) ^ galoisMult(temp[1],9) ^ \
                galoisMult(temp[0],13) ^ galoisMult(temp[3],11)
    column[3] = galoisMult(temp[3],14) ^ galoisMult(temp[2],9) ^ \
                galoisMult(temp[1],13) ^ galoisMult(temp[0],11)

# mixColumns is a wrapper for mixColumn - generates a "virtual" column from
# the state table and applies the weird galois math
def mixColumns(state):
    for i in range(4):
        column = []
        # create the column by taking the same item out of each "virtual" row
        for j in range(4):
            column.append(state[j*4+i])

        # apply mixColumn on our virtual column
        mixColumn(column)

        # transfer the new values back into the state table
        for j in range(4):
            state[j*4+i] = column[j]

# mixColumnsInv is a wrapper for mixColumnInv - generates a "virtual" column from
# the state table and applies the weird galois math
def mixColumnsInv(state):
    for i in range(4):
        column = []
        # create the column by taking the same item out of each "virtual" row
        for j in range(4):
            column.append(state[j*4+i])

        # apply mixColumn on our virtual column
        mixColumnInv(column)

        # transfer the new values back into the state table
        for j in range(4):
            state[j*4+i] = column[j]

# aesRound applies each of the four transformations in order
def aesRound(state, roundKey):
    #print "aesRound - before subBytes:", state
    subBytes(state)
    #print "aesRound - before shiftRows:", state
    shiftRows(state)
    #print "aesRound - before mixColumns:", state
    mixColumns(state)
    #print "aesRound - before addRoundKey:", state
    addRoundKey(state, roundKey)
    #print "aesRound - after addRoundKey:", state

# aesRoundInv applies each of the four inverse transformations
def aesRoundInv(state, roundKey):
    #print "aesRoundInv - before addRoundKey:", state
    addRoundKey(state, roundKey)
    #print "aesRoundInv - before mixColumnsInv:", state
    mixColumnsInv(state)
    #print "aesRoundInv - before shiftRowsInv:", state
    shiftRowsInv(state)
    #print "aesRoundInv - before subBytesInv:", state
    subBytesInv(state)
    #print "aesRoundInv - after subBytesInv:", state


# returns a 16-byte round key based on an expanded key and round number
def createRoundKey(expandedKey, n):
    return expandedKey[(n*16):(n*16+16)]

# create a key from a user-supplied password using SHA-256
#def passwordToKey(password):
#    sha256 = hashlib.sha256()
#    sha256.update(password)
#    key = []
#    for c in list(sha256.digest()):
#        key.append(ord(c))
#    return key

# wrapper function for 14 rounds of AES since we're using a 256-bit key
def aesMain(state, expandedKey, numRounds=14):
    roundKey = createRoundKey(expandedKey, 0)
    addRoundKey(state, roundKey)
    for i in range(1, numRounds):
        roundKey = createRoundKey(expandedKey, i)
        aesRound(state, roundKey)
    # final round - leave out the mixColumns transformation
    roundKey = createRoundKey(expandedKey, numRounds)
    subBytes(state)
    shiftRows(state)
    addRoundKey(state, roundKey)

# 14 rounds of AES inverse since we're using a 256-bit key
def aesMainInv(state, expandedKey, numRounds=14):
    # create roundKey for "last" round since we're going in reverse
    roundKey = createRoundKey(expandedKey, numRounds)
    # addRoundKey is the same funtion for inverse since it uses XOR
    addRoundKey(state, roundKey)
    shiftRowsInv(state)
    subBytesInv(state)
    for i in range(numRounds-1,0,-1):
        roundKey = createRoundKey(expandedKey, i)
        aesRoundInv(state, roundKey)
    # last round - leave out the mixColumns transformation
    roundKey = createRoundKey(expandedKey, 0)
    addRoundKey(state, roundKey)
    
# aesEncrypt - encrypt a single block of plaintext
def aesEncrypt(plaintext, key):
    block = copy.copy(plaintext)
    expandedKey = expandKey(key)
    aesMain(block, expandedKey)
    return block

# aesDecrypt - decrypte a single block of ciphertext
def aesDecrypt(ciphertext, key):
    block = copy.copy(ciphertext)
    expandedKey = expandKey(key)
    aesMainInv(block, expandedKey)
    return block

######################################################################
#
# Section 3: SHA256 - Secure Hash Algorithm
#
######################################################################

#!/usr/bin/python
# __author__ = 'Thomas Dixon'
# __license__ = 'MIT'

# import copy
# import struct
# import sys


# def new(m=None):
#     return sha256(m)

class sha256(object):
    _k = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
          0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
          0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
          0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
          0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
          0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
          0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
          0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
          0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
          0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
          0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
          0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
          0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
          0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
          0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
          0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
    _h = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
          0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
    _output_size = 8
    
    blocksize = 1
    block_size = 64
    digest_size = 32
    
    def __init__(self, m=None):        
        self._buffer = bytearray()
        self._counter = 0
        
        if m is not None:
            if type(m) is not bytes:
                raise TypeError('%s() argument 1 must be string, not %s' % (self.__class__.__name__, type(m).__name__))
            self.update(m)
        
    def _rotr(self, x, y):
        return ((x >> y) | (x << (32-y))) & 0xFFFFFFFF
                    
    def _sha256_process(self, c):
        w = [0]*64
        w[0:16] = struct.unpack('!16L', c)
        
        for i in range(16, 64):
            s0 = self._rotr(w[i-15], 7) ^ self._rotr(w[i-15], 18) ^ (w[i-15] >> 3)
            s1 = self._rotr(w[i-2], 17) ^ self._rotr(w[i-2], 19) ^ (w[i-2] >> 10)
            w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
        
        a,b,c,d,e,f,g,h = self._h
        
        for i in range(64):
            s0 = self._rotr(a, 2) ^ self._rotr(a, 13) ^ self._rotr(a, 22)
            maj = (a & b) ^ (a & c) ^ (b & c)
            t2 = s0 + maj
            s1 = self._rotr(e, 6) ^ self._rotr(e, 11) ^ self._rotr(e, 25)
            ch = (e & f) ^ ((~e) & g)
            t1 = h + s1 + ch + self._k[i] + w[i]
            
            h = g
            g = f
            f = e
            e = (d + t1) & 0xFFFFFFFF
            d = c
            c = b
            b = a
            a = (t1 + t2) & 0xFFFFFFFF
            
        self._h = [(x+y) & 0xFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
        
    def update(self, m):
        if not m:
            return
        if type(m) is not bytes:
            raise TypeError('%s() argument 1 must be string, not %s' % (sys._getframe().f_code.co_name, type(m).__name__))
        
        self._buffer += m
        self._counter += len(m)
        
        while len(self._buffer) >= 64:
            self._sha256_process(self._buffer[:64])
            self._buffer = self._buffer[64:]
            
    def digest(self):
        mdi = self._counter & 0x3F
        length = struct.pack('!Q', self._counter<<3)
        
        if mdi < 56:
            padlen = 55-mdi
        else:
            padlen = 119-mdi
        
        r = self.copy()
        r.update(b'\x80'+(b'\x00'*padlen)+length)
        return b''.join([struct.pack('!L', i) for i in r._h[:self._output_size]])
        
#    def hexdigest(self):
#        return self.digest()
        
    def copy(self):
        return copy.deepcopy(self)


######################################################################
#
# Section 4: BASE64 Reading & Writing
#
######################################################################

class BASE64_READER:
    def __init__(self, i):
        self.i = i          # file should be open using mode READ_TEXT
        self.cb = 0
        self.current_byte = 0
        self.got_lookahead = False
        self.lookahead = None
        self.got_error = False
        self.error_msg = False
        self.error_cnt = 0

    def BYTE_OFFSET(self, b):
        return int( b / 8 )

    def BIT_OFFSET(self, b):
        return 7 - int( b - self.BYTE_OFFSET(b)*8 )

    def DECODE_CHAR(self, ch):
        if ch >= "A" and ch <= "Z":
            return (ord(ch) - ord("A")) + 0
        elif ch >= "a" and ch <= "z":
            return (ord(ch) - ord("a")) + 26
        elif ch >= "0" and ch <= "9":
            return (ord(ch) - ord("0")) + 52
        elif ch == "+":
            return 62
        elif ch == "/":
            return 63
        else:
            self.got_error = True
            self.error_msg = "invalid base64 character '{}' ".format(ch)
            self.error_cnt = self.error_cnt + 1
            return 0

    def IS_WHITESPACE(self, ch):
        return ch.isspace()

    def GET_CHAR(self):
        if self.got_lookahead:
            self.got_lookahead = False
            return self.lookahead
        else:
            ch = self.i.read(1)
            if len(ch) == 0:
                return None
            else:
                return ch

    def UNGET_CHAR(self, ch):
        self.got_lookahead = True
        self.lookahead = ch

    def EOF(self):
        if self.got_lookahead:
            return False
        else:
            ch = self.GET_CHAR()

            while ch != None and self.IS_WHITESPACE(ch):
                ch = self.GET_CHAR()

            if ch != None:
                self.UNGET_CHAR(ch)
                return False
            else:
                return True

    def GET_REMAINDER(self):
        v = self.current_byte >> self.BIT_OFFSET(self.cb)+1
        return v

    def READ(self, n):
        result = bytearray()
        done = False
        while not done:
            ch = self.GET_CHAR()
            if ch == None:
                done = True
            elif not self.IS_WHITESPACE(ch):
                b6 = self.DECODE_CHAR(ch)
                self.ADD_6BITS(result, b6)

            if len(result) == n:
                done = True

        return result

    def ADD_6BITS(self, result, b6):
        cb = self.cb
        current_byte = self.current_byte

        b = self.BIT_OFFSET(cb)

        if b == 7:
            current_byte = b6 << 2

        elif b == 6:
            current_byte = current_byte | (b6 << 1)

        elif b == 5:
            current_byte = current_byte | b6
            result.append(current_byte)
            current_byte = 0

        elif b == 4:
            current_byte = current_byte | (b6 >> 1)
            result.append(current_byte)
            current_byte = (b6 & 1) << 7

        elif b == 3:
            current_byte = current_byte | (b6 >> 2)
            result.append(current_byte)
            current_byte = (b6 & 3) << 6

        elif b == 2:
            current_byte = current_byte | (b6 >> 3)
            result.append(current_byte)
            current_byte = (b6 & 7) << 5

        elif b == 1:
            current_byte = current_byte | (b6 >> 4)
            result.append(current_byte)
            current_byte = (b6 & 15) << 4

        elif b == 0:
            current_byte = current_byte | (b6 >> 5)
            result.append(current_byte)
            current_byte = (b6 & 31) << 3

        cb = cb + 6

        self.cb = cb
        self.current_byte = current_byte

class BASE64_WRITER:
    def __init__(self, o, remainder=0):
        self.o = o          # should be opened using mode WRITE_TEXT
        self.b6_len = 0
        self.b6 = 0
        self.col = 0
        self.remainder = remainder

    def ENCODE_B6(self, b6):
        if b6 >= 0 and b6 <= 25:
            return chr(ord("A") + b6)
        elif b6 >= 26 and b6 <= 51:
            return chr(ord("a") + (b6-26))
        elif b6 >= 52 and b6 <= 61:
            return chr(ord("0") + (b6-52))
        elif b6 == 62:
            return "+"
        else:
            return "/"

    def WRITE(self, bytes):
        for b in bytes:
            self.WRITE_BYTE(b)

    def WRITE_BYTE(self, b):
        b6 = self.b6
        b6_len = self.b6_len

        if b6_len == 0:
            b6 = b6 | (b >> 2)
            self.o.write( self.ENCODE_B6(b6) )
            b6 = (b & 0x03) << 4
            b6_len = 2

        elif b6_len == 1:
            b6 = b6 | (b >> 3)
            self.o.write( self.ENCODE_B6(b6) )
            b6 = (b & 0x07) << 3
            b6_len = 3

        elif b6_len == 2:
            b6 = b6 | (b >> 4)
            self.o.write( self.ENCODE_B6(b6) )
            b6 = (b & 0x0F) << 2
            b6_len = 4

        elif b6_len == 3:
            b6 = b6 | (b >> 5)
            self.o.write( self.ENCODE_B6(b6) )
            b6 = (b & 0x1F) << 1
            b6_len = 5

        elif b6_len == 4:
            b6 = b6 | (b >> 6)
            self.o.write( self.ENCODE_B6(b6) )
            b6 = (b & 0x3F) << 0
            self.o.write( self.ENCODE_B6(b6) )
            b6 = 0
            b6_len = 0

        elif b6_len == 5:
            b6 = b6 | (b >> 7)
            self.o.write( self.ENCODE_B6(b6) )
            b6 = (b & 0x7E) >> 1
            self.o.write( self.ENCODE_B6(b6) )
            b6 = (b) << 5
            b6_len = 1

        self.b6 = b6
        self.b6_len = b6_len

        self.col = self.col + 1
        if self.col > 46:
            self.o.write("\n")
            self.col = 0

    def CLOSE(self):
        if self.b6_len > 0:
            b = self.ENCODE_B6( self.b6 | self.remainder )
            self.o.write( b )
        self.o.write("\n")

def IS_BASE64_CHAR(ch):
    if ch >= "a" and ch <= "z":
        return True
    elif ch >= "A" and ch <= "Z":
        return True
    elif ch >= "0" and ch <= "9":
        return True
    elif ch == "+":
        return True
    elif ch == "/":
        return True
    else:
        return False

######################################################################
#
# Section 5: CREATE, ENCRYPT, DECRYPT, SIGN, VERIFY
#
######################################################################

def PAD(block, n):
    if len(block) < n:
        block.append( 0xFF )

    while len(block) < n:
        block.append( 0x00 )

def UNPAD(block):
    while block[ len(block)-1 ] == 0x00:
        block.pop(-1)
    block.pop(-1)

def XOR(mask, data):
    for i in range(len(data)):
        data[i] = data[i] ^ mask[i]

def SOLVE(x):
    #
    # solve for (+y, -y):  y^2 = x^3 + a * x + b
    #
    ysquared = S256Field(x)**3 + S256Field(A) * S256Field(x) + S256Field(B)
    y1 = ysquared.sqrt()
    y2 = S256Field(0) - y1
    if y1.num % 2 == 0:
        return (y1.num, y2.num)
    else:
        return (y2.num, y1.num)

def VALID_CURVE_X(x):
    try:
        (y1, y2) = SOLVE(x)
        S256Point(x, y1)
        S256Point(x, y2)
    except:
        return False
    return True

def VALID_SCALAR(scalar):
    return scalar >= 1 and scalar <= N-1

def INT_TO_BYTES(value, length):
    return bytearray( value.to_bytes(length, 'big') )

def BYTES_TO_INT(bytes):
    return int.from_bytes(bytes, 'big')

def POINT_TO_BYTES(P):
    x = P.x.num
    t = (2 if P.y.num % 2 == 0 else 3)
    i = (x * 256) + t
    bytes = INT_TO_BYTES(i, 33)
    return bytes

def BYTES_TO_POINT(bytes):
    if len(bytes) != 33:
        return S256Point(None, None)
    data = BYTES_TO_INT(bytes)
    x = data // 256
    if not VALID_CURVE_X(x):
        return S256Point(None, None)
    t = data % 256
    if t != 2 and t != 3:
        return S256Point(None, None)
    (y1, y2) = SOLVE(x)
    if t == 2:
        y = y1
    else:
        y = y2
    return S256Point(x, y)

def SCALE_POINT(P, scalar):
    return scalar * P

def ADD_POINTS(A, B):
    return A + B

def AES_ENCRYPT(key, bytes):
    s1 = slice(0, 16)
    s2 = slice(16, 32)
    return aesEncrypt(bytes[s1], key) + aesEncrypt(bytes[s2], key)

def AES_DECRYPT(key, bytes):
    s1 = slice(0, 16)
    s2 = slice(16, 32)
    return aesDecrypt(bytes[s1], key) + aesDecrypt(bytes[s2], key)

def SHA256(i):
    s = sha256()
    eof = False
    while not eof:
        bytes = i.read(256)
        s.update(bytes)
        if len(bytes) != 256:
            eof = True
    hashcode = BYTES_TO_INT(s.digest())
    return hashcode

def RANDOM256():
    while True:
        bytes = os.urandom(32)
        d = BYTES_TO_INT(bytes)
        if d >= 1 and d <= N-1:
            return d

def HASH_POINT(P):
    bytes = POINT_TO_BYTES(P)
    ss = io.BytesIO(bytes)
    hc = SHA256(ss)
    return INT_TO_BYTES(hc, 32)

def CREATE():
    d = RANDOM256()
    Q = SCALE_POINT(G, d)
    return (Q, d)

def ENCRYPT(Q, i, o):
    w = BASE64_WRITER(o)
    e = RANDOM256()
    E = SCALE_POINT(G, e)
    E = POINT_TO_BYTES(E)
    w.WRITE(E)
    QE = SCALE_POINT(Q, e)
    sk = HASH_POINT(QE)
    xor_mask = sk
    eof = False
    while not eof:
        plaintext_bytes = bytearray(i.read(32))
        if len(plaintext_bytes) != 32:
            PAD(plaintext_bytes, 32)
            eof = True
        XOR(xor_mask, plaintext_bytes)
        encrypted_bytes = AES_ENCRYPT(sk, plaintext_bytes)
        w.WRITE(encrypted_bytes)
        xor_mask = encrypted_bytes
    w.CLOSE()
    return None

def DECRYPT(d, i, o):
    r = BASE64_READER(i)
    E = r.READ(33)
    if len(E) != 33:
        return "decoding error. ephemeral public key len={} != 33".format(len(E))
    E = BYTES_TO_POINT(E)
    if E.x == None:
        return "decoding error: ephemeral public key is not valid"
    ED = SCALE_POINT(E, d)
    sk = HASH_POINT(ED)
    xor_mask = sk
    while not r.EOF():
        encrypted_bytes = r.READ(32)
        if len(encrypted_bytes) != 32:
            return "decoding error: truncated block. len={} (should be 32)".format(len(encrypted_bytes))
        plaintext_bytes = AES_DECRYPT(sk, encrypted_bytes)
        XOR(xor_mask, plaintext_bytes)
        if r.EOF():
            UNPAD(plaintext_bytes)
        o.write(plaintext_bytes)
        xor_mask = encrypted_bytes
    if r.got_error:
        return "error decoding base64. {}. total bad characters: {}".format(r.error_msg, r.error_cnt)
    return None

def SIGN(d, e):
    while True:
        k = RANDOM256()
        P = SCALE_POINT(G, k)
        x1 = P.x.num
        r = x1 % N
        if r == 0:
            continue
        k_inv = pow(k, N-2, N)      # fermat's little theorem
        s = k_inv * (e + d * r) % N
        if s == 0:
            continue
        break
    return (r, s)

def VERIFY(Q, e, signature):
    (r, s) = signature
    if r < 1 or r > N-1:
        return False
    if s < 1 or s > N-1:
        return False
    s_inv = pow(s, N-2, N)        # fermat's little theorem
    u1 = s_inv * e % N
    u2 = s_inv * r % N
    A = SCALE_POINT(G, u1)
    B = SCALE_POINT(Q, u2)
    P = ADD_POINTS(A, B)
    if P.x == None:
        return False
    x1 = P.x.num % N
    v = x1
    if v == r:
        return True
    else:
        return False

######################################################################
#
# Section 6: Command Line Processing
#
######################################################################

#
# A key is a 2-tuple: (key_type, data)
#
# key_type: is 0, 1, 2, or 3.
# data:     is an integer representing the private key, or public key x-coordinate
#

InvalidKey      = 0x00
PrivateKey      = 0x01
PublicKeyEvenY  = 0x02      # Public key where the Y coordinate is even
PublicKeyOddY   = 0x03      # Public key where the Y coordinate is odd

def TRIM(str):
    return str.strip()

def RIGHT_TRIM(str):
    return str.rstrip()

def REPORT_ERROR(msg):
    sys.stderr.write(msg + "\n")

def PRINT(msg):
    sys.stdout.write(msg)

def CWD():
    return os.getcwd()

def CHDIR(dir):
    try:
        os.chdir(dir)
    except:
        return "unable to change directory"
    return None

def PRINT_CWD():
    PRINT( "Current working directory: \"{}\"\n".format(CWD()) )

def DECODE_KEY(str):
    ss = io.StringIO(str)
    r = BASE64_READER(ss)
    result = r.READ(32)
    key_type = r.GET_REMAINDER()
    data = BYTES_TO_INT(result)
    return (key_type, data)

def ENCODE_KEY(key):
    (key_type, data) = key
    ss = io.StringIO()
    w = BASE64_WRITER(ss, key_type)
    bytes = INT_TO_BYTES(data, 32)
    w.WRITE(bytes)
    w.CLOSE()
    ss.seek(0)
    result = ss.read()
    result = RIGHT_TRIM(result)
    return result

def CHAR_TO_KEY_TYPE(ch):
    DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    n = DIGITS.find(ch)
    return n & 0x03

def CHECK_PUBLIC_KEY(str):
    if len(str) != 43:
        return "must be exactly 43 characters long, not {}".format(len(str))

    for i in range(0, len(str)):
        if not IS_BASE64_CHAR(str[i]):
            return "'{}' is not a base64 character".format(str[i])

    last_char = str[ len(str)-1 ]
    t = CHAR_TO_KEY_TYPE(last_char)

    if t == InvalidKey:
        return "is invalid"

    if t == PrivateKey:
        return "is not a public key (it is a private key)"

    return None

def CHECK_PRIVATE_KEY(str):
    if len(str) != 43:
        return "must be exactly 43 characters long, not {}".format(len(str))

    for i in range(0, len(str)):
        if not IS_BASE64_CHAR(str[i]):
            return "'{}' is not a base64 character".format(str[i])

    last_char = str[ len(str)-1 ]
    t = CHAR_TO_KEY_TYPE(last_char)

    if t == InvalidKey:
        return "is invalid"

    if t == PublicKeyEvenY or t == PublicKeyOddY:
        return "is not a private key (it is a public key)"

    return None

def PUBLIC_KEY_TO_POINT(key):
    (key_type, data) = key
    x = data
    if not VALID_CURVE_X(x):
        return S256Point(None, None)

    (y1, y2) = SOLVE(x)

    if key_type == PublicKeyEvenY:
        return S256Point(x, y1)
    else:
        return S256Point(x, y2)

def POINT_TO_PUBLIC_KEY(Q):
    if Q.y.num % 2 == 0:
        key_type = PublicKeyEvenY
    else:
        key_type = PublicKeyOddY
    data = Q.x.num
    return (key_type, data)

def PRIVATE_KEY_TO_SCALAR(d):
    (key_type, data) = d
    if not VALID_SCALAR(data):
        return None
    return data

def SCALAR_TO_PRIVATE_KEY(x):
    return (PrivateKey, x)

def IS_HASHCODE(str):
    if len(str) != 64:
        return False
    for i in range(0, 64):
        if str[i] not in "abcdef0123456789":
            return False
    return True

def DECODE_HASHCODE(str):
    return int(str, 16)

def ENCODE_HASHCODE(hc):
    return format( hc, '064x' )

def CHECK_INPUT(input_file):
    if input_file == "-":
        return None

    if not os.path.exists(input_file):
        return "no such file"

    if not os.path.isfile(input_file):
        return "not a regular file"

    try:
        f = open(input_file, "r")
        f.close()
    except:
        return "cannot be opened for reading"

    return None

def CHECK_OUTPUT(output_file):
    if output_file == "-":
        return None

    if os.path.exists(output_file):
        if os.path.isfile(output_file):
            try:
                f = open(output_file, "a")
                f.close()
            except:
                return "cannot be opened for writing"
        else:
            return "not a regular file"
    else:
        try:
            f = open(output_file, "w")
            f.close()
        except:
            return "cannot be created for writing"

    return None

#
# modes for OPEN_FILE()
#
READ_TEXT    = "r"
READ_BINARY  = "rb"
WRITE_TEXT   = "w"
WRITE_BINARY = "wb"

def OPEN_FILE(filename, mode):
    if filename == "-":
        if mode == READ_TEXT or mode == READ_BINARY:
            # solved with this: https://bugs.python.org/issue4571
            if mode == READ_BINARY:         
                return sys.stdin.buffer
            else:
                return sys.stdin
        else:
            # solved with this: https://bugs.python.org/issue4571
            if mode == WRITE_BINARY:
                return sys.stdout.buffer
            else:
                return sys.stdout

    return open(filename, mode)

def CLOSE_FILE(file_name, f):
    if file_name != "-":
        f.close()

def USAGE(msg):
    PRINT("""
Usage:
    pek create
    pek encrypt  <public-key>      <input>        <output>
    pek decrypt  <private-key>     <input>        <output>
    pek sign     <private-key>  <file-or-hash>  <signature-out>
    pek verify   <public-key>   <file-or-hash>  <signature-in>
    pek help
    pek              Interactive Menu!!!

    <public-key>     This is a public key obtained from calling "pek create".
    <private-key>    This is a private key obtained from calling "pek create".
    <input>          This is the filename to read, use "-" to read from standard input.
    <output>         This is the filename to write, use "-" to write to standard output.
    <file-or-hash>   The filename to read and calculate the hash code for. Use "-" for standard input.
                     A hash code can be given instead. This is a 64-character string consisting of hex digits.
    <signature-out>  This is the signature filename to create, use "-" for standard output.
    <signature-in>   This is the signature filename to read, use "-" for standard output.

{}
""".format(msg))

def CMD_CREATE(args):
    if len(args) != 2:
        USAGE("too many arguments for 'create'")
        return

    (Q, d) = CREATE()

    pub = POINT_TO_PUBLIC_KEY(Q)
    priv = SCALAR_TO_PRIVATE_KEY(d)

    PRINT("Public key:  {}\n".format( ENCODE_KEY(pub) ))
    PRINT("Private key: {}\n".format( ENCODE_KEY(priv) ))

def CMD_ENCRYPT(args):
    if len(args) != 5:
        USAGE("incorrect number of arguments for 'encrypt'")
        return

    public_key = args[2]
    input_file = args[3]
    output_file = args[4]

    rc = CHECK_PUBLIC_KEY(public_key)
    if rc != None:
        USAGE("{}: <public-key> argument {}".format(public_key, rc))
        return

    pub = DECODE_KEY(public_key)
    Q = PUBLIC_KEY_TO_POINT(pub)
    if Q.x == None:
        USAGE("{}: <public-key> argument is not valid".format(public_key))
        return

    rc = CHECK_INPUT(input_file)
    if rc != None:
        USAGE("{}: {}".format(input_file, rc))
        return

    rc = CHECK_OUTPUT(output_file)
    if rc != None:
        USAGE("{}: {}".format(output_file, rc))
        return

    i = OPEN_FILE(input_file, READ_BINARY)
    o = OPEN_FILE(output_file, WRITE_TEXT)

    rc = ENCRYPT(Q, i, o)

    CLOSE_FILE(input_file, i)
    CLOSE_FILE(output_file, o)

    if rc != None:
        REPORT_ERROR("encrypt: {}".format(rc))
        return

def CMD_DECRYPT(args):
    if len(args) != 5:
        USAGE("incorrect number of arguments for 'decrypt'")
        return

    private_key = args[2]
    input_file = args[3]
    output_file = args[4]

    rc = CHECK_PRIVATE_KEY(private_key)
    if rc != None:
        USAGE("{}: <private-key> argument {}".format(private_key, rc))
        return

    priv = DECODE_KEY(private_key)
    d = PRIVATE_KEY_TO_SCALAR(priv)
    if d == None:
        USAGE("{}: <private-key> argument invalid scalar".format(private_key))
        return

    rc = CHECK_INPUT(input_file)
    if rc != None:
        USAGE("{}: {}".format(input_file, rc))
        return

    rc = CHECK_OUTPUT(output_file)
    if rc != None:
        USAGE("{}: {}".format(output_file, rc))
        return

    i = OPEN_FILE(input_file, READ_TEXT)
    o = OPEN_FILE(output_file, WRITE_BINARY)

    rc = DECRYPT(d, i, o)

    CLOSE_FILE(input_file, i)
    CLOSE_FILE(output_file, o)

    if rc != None:
        REPORT_ERROR("decrypt: {}".format(rc))
        return

def CMD_SIGN(args):
    if len(args) != 5:
        USAGE("incorrect number of arguments for 'sign'")
        return

    private_key = args[2]
    input_file = args[3]
    sig_output = args[4]

    rc = CHECK_PRIVATE_KEY(private_key)
    if rc != None:
        USAGE("{}: <private-key> argument {}".format(private_key, rc))
        return

    priv = DECODE_KEY(private_key)
    d = PRIVATE_KEY_TO_SCALAR(priv)
    if d == None:
        USAGE("{}: <private-key> argument invalid scalar".format(private_key))
        return

    rc = CHECK_OUTPUT(sig_output)
    if rc != None:
        USAGE("{}: {}".format(sig_output, rc))
        return

    if IS_HASHCODE(input_file):
        e = DECODE_HASHCODE(input_file)
    else:
        rc = CHECK_INPUT(input_file)
        if rc != None:
            USAGE("{}: {}".format(input_file, rc))
            return
        i = OPEN_FILE(input_file, READ_BINARY)
        e = SHA256(i)
        CLOSE_FILE(input_file, i)

    (r, s) = SIGN(d, e)

    o = OPEN_FILE(sig_output, WRITE_TEXT)
    w = BASE64_WRITER(o)
    w.WRITE( INT_TO_BYTES(r, 32) )
    w.WRITE( INT_TO_BYTES(s, 32) )
    w.CLOSE()
    CLOSE_FILE(sig_output, o)

def CMD_VERIFY(args):
    if len(args) != 5:
        USAGE("incorrect number of arguments for 'verify'")
        return

    public_key = args[2]
    input_file = args[3]
    sig_input = args[4]

    if input_file == "-" and sig_input == "-":
        USAGE("using \"-\" for both <sig-input> and <input_file> is not allowed")
        return

    rc = CHECK_PUBLIC_KEY(public_key)
    if rc != None:
        USAGE("{}: <public-key> argument {}".format(public_key, rc))
        return

    pub = DECODE_KEY(public_key)
    Q = PUBLIC_KEY_TO_POINT(pub)
    if Q.x == None:
        USAGE("{}: <public-key> argument is not valid".format(public_key))
        return

    if IS_HASHCODE(input_file):
        e = DECODE_HASHCODE(input_file)
    else:
        rc = CHECK_INPUT(input_file)
        if rc != None:
            USAGE("{}: {}".format(input_file, rc))
            return
        i = OPEN_FILE(input_file, READ_BINARY)
        e = SHA256(i)
        CLOSE_FILE(input_file, i)

    rc = CHECK_INPUT(sig_input)
    if rc != None:
        USAGE("{}: {}".format(sig_input, rc))
        return

    i = OPEN_FILE(sig_input, READ_TEXT)

    rdr = BASE64_READER(i)
    r = rdr.READ(32)
    if len(r) != 32:
        USAGE("{}: error reading signature. unable to read r. len(32) != len({})".format(sig_input, len(r)))
        return

    s = rdr.READ(32)
    if len(s) != 32:
        USAGE("{}: error reading signature. unable to read s. len(32) != len({})".format(sig_input, len(s)))
        return

    if not rdr.EOF():
        USAGE("{}: error reading signature. data stream too long".format(sig_input))
        return

    CLOSE_FILE(sig_input, i)

    r = BYTES_TO_INT(r)
    s = BYTES_TO_INT(s)

    good = VERIFY(Q, e, (r, s) )

    if good:
        PRINT("GOOD\n")
    else:
        PRINT("BAD\n")

def CMD_HELP():
    USAGE("")
    PRINT("""\
Examples:
    $ pek create
    Public key:  9ypjamC46AgSmFQtTfS9gxFPzIqz2TRpXT2kczCNDyK
    Private key: m8G/I3s9Qc2KT1w8iT/IhtPNUfVmJbzkBUM0ewkGkTt

    $ pek encrypt 9ypjamC46AgSmFQtTfS9gxFPzIqz2TRpXT2kczCNDyK f1.txt f1.pek
    $ pek decrypt m8G/I3s9Qc2KT1w8iT/IhtPNUfVmJbzkBUM0ewkGkTt f1.pek original.txt

    $ pek sign    m8G/I3s9Qc2KT1w8iT/IhtPNUfVmJbzkBUM0ewkGkTt MANIFESTO.doc  MANIFESTO.sig
    $ pek verify  9ypjamC46AgSmFQtTfS9gxFPzIqz2TRpXT2kczCNDyK MANIFESTO.doc  MANIFESTO.sig
    GOOD

    $ shasum -a 256 MANIFESTO.doc
    6a82de9253f887f88e5a537dc9d8749a66fc28597fd3da60850ba3dd9acf085b

    $ pek sign   lZCBMbLb8GP/IkeKcPVCeoNLju/ynXsC6MZzm3D3ASk 6a82de9253f887f88e5a537dc9d8749a66fc28597fd3da60850ba3dd9acf085b MANIFESTO.sig
    $ pek verify UwcQDXZlP1kWjda3ngcJ4HzWsz+C4Ahth5ieidwu8n4 6a82de9253f887f88e5a537dc9d8749a66fc28597fd3da60850ba3dd9acf085b MANIFESTO.sig
    GOOD

  C:\> pek create
  Public key:  HFpMb+pWfyB7LXqepl3cRcbSW/6kgEUDT8hZ5iyuF9u
  Private key: 94PHU/PoN++CvWrguXQSTNOaq0rBH1geUJHPd0oCrbR

  C:\> pek encrypt HFpMb+pWfyB7LXqepl3cRcbSW/6kgEUDT8hZ5iyuF9u f1.txt f2.pek
  C:\> pek decrypt 94PHU/PoN++CvWrguXQSTNOaq0rBH1geUJHPd0oCrbR f2.pek f3.txt
      The files f1.txt and f3.txt will be the same.

  C:\> pek sign 94PHU/PoN++CvWrguXQSTNOaq0rBH1geUJHPd0oCrbR f1.txt f1.sig
  C:\> pek verify HFpMb+pWfyB7LXqepl3cRcbSW/6kgEUDT8hZ5iyuF9u f1.txt f1.sig
  GOOD
      Only the private key holder can create a good signature.

 * Keep your PRIVATE KEY secret.
 * You can freely give out your PUBLIC KEY.
 * Encrypt files using the recipient's public key.
 * Decrypt recieved files using your private key.
 * Create a signature using your private key.
 * Verify a signature using the senders public key.
 * Enter "-" for the input file to read from the keyboard.
 * Enter "-" for the output file to write to the screen.
 * Use a single '.' (period) on a line by itself to signal end-of-file while typing.
 * Use ^D to signal end-of-file while typing on MacOS/Linux.   (press CTRL and D keys)
 * Use ^Z to signal end-of-file while typing on Windows.       (press CTRL and Z keys)
""".format())

######################################################################
#
# Section 7: Interactive Menu
#
######################################################################

DQK  = ""   # Default public key
DDK  = ""   # Default private key
DF   = ""   # Default file name
DDIR = ""   # Default Directory

def INPUT(prompt):
    try:
        str = input(prompt)
    except:
        return None # None means EOF
    return str

def INPUT_WITH_DEFAULT(prompt, df):
    if df != "":
        prompt = prompt + " [" + df + "]: "
    else:
        prompt = prompt + ": "

    str = INPUT(prompt)
    if str == None:
        return None
    elif str == "":
        return df
    else:
        return str

def READ_CONSOLE():
    PRINT("Enter message (use '.' to finish):\n")

    all_lines = ""
    eof = False
    while not eof:
        str = INPUT("")
        if str == None:
            eof = True
        elif RIGHT_TRIM(str) == ".":
            eof = True
        else:
            all_lines = all_lines + str + "\n"

    return all_lines

def MENU_CREATE():
    global DQK
    global DDK

    (Q, d) = CREATE()

    pub = POINT_TO_PUBLIC_KEY(Q)
    priv = SCALAR_TO_PRIVATE_KEY(d)

    DQK = ENCODE_KEY(pub)
    DDK = ENCODE_KEY(priv)
    PRINT("Public key:  {}\n".format(DQK))
    PRINT("Private key: {}\n".format(DDK))
    return False

def MENU_ENCRYPT():
    global DQK
    global DF

    PRINT_CWD()
    public_key = INPUT_WITH_DEFAULT("Enter public key", DQK)
    if public_key == None:
        return True

    rc = CHECK_PUBLIC_KEY(public_key)
    if rc != None:
        REPORT_ERROR("Error entering public key '{}': {}.".format(public_key, rc))
        return False

    pub = DECODE_KEY(public_key)
    Q = PUBLIC_KEY_TO_POINT(pub)
    if Q.x.num == None:
        REPORT_ERROR("Error entering public key '{}' is not valid.".format(public_key))
        return False

    input_file = INPUT_WITH_DEFAULT("Enter file to encrypt", DF)
    if input_file == None:
        return True

    rc = CHECK_INPUT(input_file)
    if rc != None:
        REPORT_ERROR("Error entering file to encrypt '{}': {}.".format(input_file, rc))
        return False

    output_file = INPUT("Enter output file: ")
    if output_file == None:
        return True

    rc = CHECK_OUTPUT(output_file)
    if rc != None:
        REPORT_ERROR("Error entering output file '{}': {}.".format(output_file, rc))
        return False

    if input_file == "-":
        all_lines = READ_CONSOLE()
        i = io.BytesIO(bytes(all_lines, 'utf-8'))
    else:
        i = OPEN_FILE(input_file, READ_BINARY)

    o = OPEN_FILE(output_file, WRITE_TEXT)

    rc = ENCRYPT(Q, i, o)

    CLOSE_FILE(input_file, i)
    CLOSE_FILE(output_file, o)

    if rc == None:
        PRINT("Encrypted the file \"{}\". Output written to \"{}\".\n".format(input_file, output_file))
        DQK = public_key
        DF = output_file
    else:
        REPORT_ERROR("Error encrypting file '{}': {}.".format(input_file, rc))

    return False

def MENU_DECRYPT():
    global DDK
    global DF

    PRINT_CWD()
    private_key = INPUT_WITH_DEFAULT("Enter private key", DDK)
    if private_key == None:
        return True

    rc = CHECK_PRIVATE_KEY(private_key)
    if rc != None:
        REPORT_ERROR("Error entering private key '{}' {}.".format(private_key, rc))
        return False

    priv = DECODE_KEY(private_key)
    d = PRIVATE_KEY_TO_SCALAR(priv)
    if d == None:
        REPORT_ERROR("encrypt: private key '{}' invalid scalar".format(private_key))

    input_file = INPUT_WITH_DEFAULT("Enter file to decrypt", DF)
    if input_file == None:
        return True

    rc = CHECK_INPUT(input_file)
    if rc != None:
        REPORT_ERROR("Error entering file to decrypt '{}' {}.".format(input_file, rc))
        return False

    output_file = INPUT("Enter output file: ")
    if output_file == None:
        return True

    rc = CHECK_OUTPUT(output_file)
    if rc != None:
        REPORT_ERROR("Error entering output file '{}' {}.".format(output_file, rc))
        return False

    if input_file == "-":
        all_lines = READ_CONSOLE()
        i = io.StringIO(all_lines)
    else:
        i = OPEN_FILE(input_file, READ_TEXT)

    o = OPEN_FILE(output_file, WRITE_BINARY)

    rc = DECRYPT(d, i, o)

    CLOSE_FILE(input_file, i)
    CLOSE_FILE(output_file, o)

    if rc == None:
        PRINT("Decrypted the file \"{}\". Output written to \"{}\".\n".format(input_file, output_file))
        DDK = private_key
        DF = output_file
    else:
        REPORT_ERROR("Error decrypting file '{}': {}.".format(input_file, rc))

    return False

def MENU_SIGN():
    global DDK
    global DF

    PRINT_CWD()
    private_key = INPUT_WITH_DEFAULT("Enter private key", DDK)
    if private_key == None:
        return True

    rc = CHECK_PRIVATE_KEY(private_key)
    if rc != None:
        REPORT_ERROR("Error entering private key '{}' {}.".format(private_key, rc))
        return False

    priv = DECODE_KEY(private_key)
    d = PRIVATE_KEY_TO_SCALAR(priv)
    if d == None:
        REPORT_ERROR("Error entering private key '{}' invalid scalar.".format(private_key))
        return False

    input_file = INPUT_WITH_DEFAULT("Enter file or hash code", DF)
    if input_file == None:
       return True

    if IS_HASHCODE(input_file):
        e = DECODE_HASHCODE(input_file)
    else:
        rc = CHECK_INPUT(input_file)
        if rc != None:
            REPORT_ERROR("Error entering file '{}' {}.".format(input_file, rc))
            return False

        if input_file == "-":
            all_lines = READ_CONSOLE()
            i = io.BytesIO(bytes(all_lines, 'utf-8'))
        else:
            i = OPEN_FILE(input_file, READ_BINARY)

        e = SHA256(i)
        CLOSE_FILE(input_file, i)

    PRINT("The sha256 hash code to be signed is:\n {}\n".format( ENCODE_HASHCODE(e) ))

    sig_output = INPUT("Enter signature file to output: ")
    if sig_output == None:
       return True

    rc = CHECK_OUTPUT(sig_output)
    if rc != None:
        REPORT_ERROR("Error entering signature file '{}' {}.".format(sig_output, rc))
        return False

    (r, s) = SIGN(d, e)

    o = OPEN_FILE(sig_output, WRITE_TEXT)
    w = BASE64_WRITER(o)
    w.WRITE( INT_TO_BYTES(r, 32) )
    w.WRITE( INT_TO_BYTES(s, 32) )
    w.CLOSE()
    CLOSE_FILE(sig_output, o)

    if IS_HASHCODE(input_file):
        PRINT("Created a signature for the hash code. Output written to \"{}\".\n".format(sig_output))
    else:
        PRINT("Created a signature for the file \"{}\". Output written to \"{}\".\n".format(input_file, sig_output))

    DF = sig_output
    DDK = private_key
    return False

def PRINT_GOOD(sig_input, input_file, e):
    PRINT("GOOD\n")
    if IS_HASHCODE(input_file):
        PRINT("The file \"{}\" contains a valid signature for the sha256 hash code:\n".format(sig_input))
        PRINT(" {}\n".format( ENCODE_HASHCODE(e) ))
    else:
        PRINT("The file \"{}\" contains a valid signature for the file \"{}\".\n".format(sig_input, input_file))
        PRINT("Having sha256 hash code:\n")
        PRINT(" {}\n".format( ENCODE_HASHCODE(e) ))

def PRINT_BAD(sig_input, input_file, e):
    PRINT("BAD\n")
    if IS_HASHCODE(input_file):
        PRINT("The file \"{}\" DOES NOT contain a valid signature for the sha256 hash code:\n".format(sig_input))
        PRINT(" {}\n".format( ENCODE_HASHCODE(e) ))
    else:
        PRINT("The file \"{}\" DOES NOT contain a valid signature for the file \"{}\".\n".format(sig_input, input_file))
        PRINT("Having sha256 hash code:\n")
        PRINT(" {}\n".format( ENCODE_HASHCODE(e) ))

def MENU_VERIFY():
    global DQK
    global DF

    PRINT_CWD()
    public_key = INPUT_WITH_DEFAULT("Enter public key", DQK)
    if public_key == None:
        return True

    rc = CHECK_PUBLIC_KEY(public_key)
    if rc != None:
        REPORT_ERROR("Error entering public key '{}': {}.".format(public_key, rc))
        return False

    pub = DECODE_KEY(public_key)
    Q = PUBLIC_KEY_TO_POINT(pub)
    if Q.x == None:
        REPORT_ERROR("Error entering public key '{}': not valid.".format(public_key))
        return False

    input_file = INPUT_WITH_DEFAULT("Enter file or hash code", DF)
    if input_file == None:
        return True

    if IS_HASHCODE(input_file):
        e = DECODE_HASHCODE(input_file)
    else:
        rc = CHECK_INPUT(input_file)
        if rc != None:
            REPORT_ERROR("Error entering file '{}' {}.".format(input_file, rc))
            return False

        if input_file == "-":
            all_lines = READ_CONSOLE()
            i = io.BytesIO(bytes(all_lines, 'utf-8'))
        else:
            i = OPEN_FILE(input_file, READ_BINARY)

        e = SHA256(i)
        CLOSE_FILE(input_file, i)

    PRINT("The sha256 hash code to be verified is:\n")
    PRINT(" {}\n".format( ENCODE_HASHCODE(e) ))

    sig_input = INPUT("Enter signature file: ")
    if sig_input == None:
       return True

    rc = CHECK_INPUT(sig_input)
    if rc != None:
        REPORT_ERROR("Error entering signature file '{}' {}.".format(sig_input, rc))
        return False

    if sig_input == "-":
        all_lines = READ_CONSOLE()
        i = io.StringIO(all_lines)
    else:
        i = OPEN_FILE(sig_input, READ_TEXT)

    rdr = BASE64_READER(i)
    r = rdr.READ(32)
    if len(r) != 32:
        REPORT_ERROR("{}: error reading signature. unable to read r. len(32) != len({})".format(sig_input, len(r)))
        return False

    s = rdr.READ(32)
    if len(s) != 32:
        REPORT_ERROR("{}: error reading signature. unable to read s. len(32) != len({})".format(sig_input, len(s)))
        return False

    if not rdr.EOF():
        REPORT_ERROR("{}: error reading signature. data stream too long".format(sig_input))
        return False

    CLOSE_FILE(sig_input, i)

    r = BYTES_TO_INT(r)
    s = BYTES_TO_INT(s)

    good = VERIFY(Q, e, (r, s))

    if good:
        PRINT_GOOD(sig_input, input_file, e)
    else:
        PRINT_BAD(sig_input, input_file, e)

    DF = input_file
    DQK = public_key

    return False

def MENU_SHOWFILE():
    global DF

    PRINT_CWD()
    input_file = INPUT_WITH_DEFAULT("Enter file to show", DF)
    if input_file == None:
        return True

    if input_file == "-":
        REPORT_ERROR("Invalid filename \"-\"")
        return False

    rc = CHECK_INPUT(input_file)
    if rc != None:
        REPORT_ERROR("{}: {}".format(input_file, rc))
        return False

    PRINT("Showing file: \"{}\".\n".format(input_file))

    f = OPEN_FILE(input_file, READ_TEXT)
    str = f.read()
    CLOSE_FILE(input_file, f)
    PRINT("--- cut here ---\n")
    PRINT(str)
    if str[ len(str)-1 ] != "\n":
        PRINT("\n")
    PRINT("--- cut here ---\n")
    DF = input_file
    return False

def MENU_CREATEFILE():
    global DF

    PRINT_CWD()
    input_file = INPUT("Enter file to create: ")
    if input_file == None:
        return True

    if input_file == "-":
        REPORT_ERROR("Invalid filename \"-\"")
        return False

    rc = CHECK_OUTPUT(input_file)
    if rc != None:
        REPORT_ERROR("{}: {}".format(input_file, rc))
        return False

    all_lines = READ_CONSOLE()

    f = OPEN_FILE(input_file, WRITE_TEXT)
    f.write(all_lines)
    CLOSE_FILE(input_file, f)
    PRINT("\n")
    PRINT("Created file: \"{}\".\n".format(input_file))
    DF = input_file
    return False

def MENU_CD():
    global DDIR

    PRINT_CWD()
    dir = INPUT_WITH_DEFAULT("Change working directory to", DDIR)
    if dir == None:
        return True
    prev_dir = CWD()
    rc = CHDIR(dir)
    if rc == None:
        PRINT_CWD()
        DDIR = prev_dir
    else:
        REPORT_ERROR("{}: {}\n".format(dir, rc))
        return False
    return False

def MENU_DIR():
    PRINT_CWD()
    files = os.listdir(".")
    files.sort()
    for f in files:
        PRINT("  \"{}\"\n".format(f))
    PRINT("\n")
    return False

def MENU_FILE_OPERATIONS():
    Over = -1
    Under = -2
    Bad = -3

    last_error = None
    while True:
        PRINT("""
1) Show File
2) Create File
3) Change Directory
4) List Directory
5) Return To Main Menu
""")

        if last_error != None:
            PRINT("\n" + last_error + "\n")
            last_error = None

        str = INPUT("Choose 1-5? ")
        if str == None:
            PRINT("\n")
            return True
        elif TRIM(str) == "":
            continue

        try:
            choice = int(str)
            if choice > 5:
                choice = Over
            elif choice < 1:
                choice = Under
        except:
            choice = Bad

        PRINT("\n")

        if choice == 1:
            exit_program = MENU_SHOWFILE()
            return exit_program
        elif choice == 2:
            exit_program = MENU_CREATEFILE()
            return exit_program
        elif choice == 3:
            exit_program = MENU_CD()
            return exit_program
        elif choice == 4:
            exit_program = MENU_DIR()
            return exit_program
        elif choice == 5:
            PRINT("\n")
            return False
        elif choice == Over:
            last_error = "{} is too big! Enter a number between 1 and 5.\n".format(str)
        elif choice == Under:
            last_error = "{} is too small! Enter a number between 1 and 5.\n".format(str)
        elif choice == Bad:
            last_error = "Invalid choice '{}', enter a number between 1 and 5.\n".format(str)

def MENU_HELP():
    CMD_HELP()
    return False

def MENU_QUIT():
    return True

def MENU():
    Over = -1
    Under = -2
    Bad = -3

    last_error = None
    done = False
    while not done:
        PRINT("""
1) Create Keys
2) Encrypt file
3) Decrypt file
4) Create Signature
5) Verify Signature
6) File Operations
7) Help
8) Quit
""")

        if last_error != None:
            PRINT("\n" + last_error + "\n")
            last_error = None

        str = INPUT("Choose 1-8? ")
        if str == None:
            str = "8"
        elif TRIM(str) == "":
            continue

        try:
            choice = int(str)
            if choice > 8:
                choice = Over
            elif choice < 1:
                choice = Under
        except:
            choice = Bad

        PRINT("\n")

        if choice == 1:
            done = MENU_CREATE()
        elif choice == 2:
            done = MENU_ENCRYPT()
        elif choice == 3:
            done = MENU_DECRYPT()
        elif choice == 4:
            done = MENU_SIGN()
        elif choice == 5:
            done = MENU_VERIFY()
        elif choice == 6:
            done = MENU_FILE_OPERATIONS()
        elif choice == 7:
            done = MENU_HELP()
        elif choice == 8:
            done = MENU_QUIT()
        elif choice == Over:
            last_error = "{} is too big! Enter a number between 1 and 8.\n".format(str)
        elif choice == Under:
            last_error = "{} is too small! Enter a number between 1 and 8.\n".format(str)
        elif choice == Bad:
            last_error = "Invalid choice '{}', enter a number between 1 and 8.\n".format(str)

######################################################################
#
# Section 8: MAIN()
#
######################################################################

def MAIN():
    if len(sys.argv) <= 1:
        MENU()
    else:
        command = sys.argv[1]

        if command == "create":
            CMD_CREATE(sys.argv)
        elif command == "encrypt":
            CMD_ENCRYPT(sys.argv)
        elif command == "decrypt":
            CMD_DECRYPT(sys.argv)
        elif command == "sign":
            CMD_SIGN(sys.argv)
        elif command == "verify":
            CMD_VERIFY(sys.argv)
        elif command == "help":
            CMD_HELP()
        else:
            USAGE( "Unknown command '{}'.".format(command) )

MAIN()
