aboutsummaryrefslogblamecommitdiff
path: root/test_vectors/format_random_test_vector.py
blob: 861bab5956b26e8c159ba092ef65aab58eb4594a (plain) (tree)






































































































































































































































































































































                                                                                                                  
#
# format_random_test_vector.py
# ------------------------------------------
# Formats test vector for ecdsa_fpga_model
#
# Author: Pavel Shatov
# Copyright (c) 2017-2018, NORDUnet A/S
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# - Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
# - Neither the name of the NORDUnet nor the names of its contributors may
#   be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#


#
USAGE = "USAGE: format_random_test_vector.py [openssl_binary]"
#


#
# This script reads the test vector generated by the script
# regenerate_random_test_vector.py and writes nicely formatted C header file
# and Verilog include file.
#


#
# imports
#
import sys
import subprocess


#
# list of curve names of interest
#
CURVE_P256 = "p256"
CURVE_P384 = "p384"


#
# variables
#
OPENSSL = ""


#
# format one test vector
#
def format_c_header(f, curve, da, qax, qay):

    if curve == CURVE_P256: curve_str = "ECDSA_P256"
    if curve == CURVE_P384: curve_str = "ECDSA_P384"
    
        # write all numbers in vector
    format_c_array(f, da,  "#define " + curve_str + "_D_RANDOM_INIT"  + " \\\n")
    format_c_array(f, qax, "#define " + curve_str + "_QX_RANDOM_INIT" + " \\\n")
    format_c_array(f, qay, "#define " + curve_str + "_QY_RANDOM_INIT" + " \\\n")
    
    
#
# format one test vector
#
def format_verilog_include(f, curve, da, qax, qay):

    if curve == CURVE_P256: curve_str, msb_index = "ECDSA_P256", "255"
    if curve == CURVE_P384: curve_str, msb_index = "ECDSA_P384", "383"

        # write all numbers in vector
    format_verilog_concatenation(f, da,   "localparam [" + msb_index + ":0] " + curve_str + "_D_RANDOM " + " =\n")
    format_verilog_concatenation(f, qax,  "localparam [" + msb_index + ":0] " + curve_str + "_QX_RANDOM" + " =\n")
    format_verilog_concatenation(f, qay,  "localparam [" + msb_index + ":0] " + curve_str + "_QY_RANDOM" + " =\n")


#
# nicely format multi-word integer into C array initializer
#
def format_c_array(f, n, s):

        # print '#define XYZ \'
    f.write(s)

        # convert number to hex string and prepend it with zeroes if necessary
    n_hex = hex(n).lstrip("0x").rstrip("L")
    while (len(n_hex) % 8) > 0:
        n_hex = "0" + n_hex
    
        # get number of 32-bit words
    num_words = len(n_hex) // 8

        # print all words in n
    w = 0
    while w < num_words:
    
        n_part = ""

            # add tab for every new line
        if w == 0:
            n_part += "\t{"
        elif (w % 4) == 0:
            n_part += "\t "
            
            # add current word
        n_part += "0x" + n_hex[8 * w : 8 * (w + 1)]
        
            # add separator or newline
        if (w + 1) == num_words:
            n_part += "}\n"
        else:
            n_part += ", "
            if (w % 4) == 3:
                n_part += "\\\n"        
                
        w += 1
        
            # write current part
        f.write(n_part)
    
        # write final newline
    f.write("\n")


def format_verilog_concatenation(f, n, s):

        # print 'localparam ZZZ ='
    f.write(s)
    
        # convert number to hex string and prepend it with zeroes if necessary
    n_hex = hex(n).split("0x")[1]
    while (len(n_hex) % 8) > 0:
        n_hex = "0" + n_hex
    
        # get number of 32-bit words
    num_words = len(n_hex) // 8

        # print all words in n
    w = 0
    while w < num_words:
    
        n_part = ""
        
        if w == 0:
            n_part += "\t{"
        elif (w % 4) == 0:
            n_part += "\t "
            
        n_part += "32'h" + n_hex[8 * w : 8 * (w + 1)]
        
        if (w + 1) == num_words:
            n_part += "};\n"
        else:
            n_part += ", "
            if (w % 4) == 3:
                n_part += "\n"      
        w += 1
        
        f.write(n_part)
    
    f.write("\n")


    #
    # returns d, qx, qy, where
    # d is private key and qx, qy is the corresponding public key
    #
def get_key(openssl, party, curve):

        # generate private key filename
    key_file = party + "_" + curve + ".key"
        
        # retrieve key components using openssl
    openssl_command = [openssl, "ec", "-in", key_file, "-noout", "-text"] 
    openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8")
    stdout_lines = openssl_stdout.splitlines()
    
    found_priv = False
    found_pub = False
    
    key_priv = ""
    key_pub = ""
    
        # process lines looking for "priv:" and "pub:" markers
    for line in stdout_lines:
    
            # found private key marker?
        if line.strip() == "priv:":
            found_priv = True
            found_pub = False
            continue
            
            # found public key marker?
        if line.strip() == "pub:":      # openssl 1.0.2g prints 'pub: ' (extra space before newline),
            found_pub = True            # so we need to compare against line.strip(), not just line
            found_priv = False
            continue
            
            # found part of private key?
        if found_priv:
            if not line.startswith(" "):
                found_priv = False
                continue
            else:
                key_priv += line.strip()
            
            # found part of public key?
        if found_pub:
            if not line.startswith(" "):
                found_pub = False
                continue
            else:
                key_pub += line.strip()
            
        # do some cleanup and sanity checking on private key
        #  * remove extra leading zero byte if present
        #  * remove colons
        #  * check length (256 bits or 384 bits)
    while key_priv.startswith("00"):
        key_priv = key_priv[2:]
        
    key_priv = key_priv.replace(":", "")
        
    if curve == CURVE_P256 and len(key_priv) != 256 / 4: sys.exit()
    if curve == CURVE_P384 and len(key_priv) != 384 / 4: sys.exit()
    
        # do some cleanup and sanity checking on public key
        #  * make sure, that uncompressed form marker (0x04) is present and
        #    then remove it
        #  * remove colons
        #  * check length (2x256 or 2x384 bits)
    if not key_pub.startswith("04"): sys.exit()
        
    key_pub = key_pub[2:]
    key_pub = key_pub.replace(":", "")

    if curve == CURVE_P256 and len(key_pub) != 2 * 256 / 4: sys.exit()
    if curve == CURVE_P384 and len(key_pub) != 2 * 384 / 4: sys.exit()
    
        # split public key into parts
    if curve == CURVE_P256:
        key_pub_x = key_pub[  0: 64]
        key_pub_y = key_pub[ 64:128]
    
    if curve == CURVE_P384:
        key_pub_x = key_pub[  0: 96]
        key_pub_y = key_pub[ 96:192]
    
        # convert from strings to integers
    key_priv = int(key_priv, 16)
    key_pub_x = int(key_pub_x, 16)
    key_pub_y = int(key_pub_y, 16)
            
        # done
    return key_priv, key_pub_x, key_pub_y

    
if __name__ == "__main__":

    # detect whether user requested some specific binary
    if len(sys.argv) == 1:
        OPENSSL = "openssl"
        print("Using system OpenSSL library.")
    elif len(sys.argv) == 2:
        OPENSSL = sys.argv[1]
        print("Using OpenSSL binary '" + OPENSSL + "'...")
    else:
        print(USAGE)
    
    if len(OPENSSL) > 0:
    
        # list of curves to process
        curves = [CURVE_P256, CURVE_P384]
    
        # open output files
        file_h = open('ecdsa_test_vector_randomized.h',  'w')
        file_v = open('ecdsa_test_vector_randomized.vh', 'w')
    
        # write headers
        file_h.write("/* Generated automatically, do not edit. */\n\n")
        file_v.write("/* Generated automatically, do not edit. */\n\n")
    
        # process all the keys
        for next_curve in curves:
    
            # load keys
            da, qax, qay = get_key(OPENSSL, "charlie", next_curve)
    
            # format numbers and write to files

            format_c_header(file_h, next_curve,
                da, qax, qay);
    
            format_verilog_include(file_v, next_curve,
                da, qax, qay);
    
        # done
        file_h.close()
        file_v.close()
    
        # everything went just fine
        print("Test vector formatted.")

#
# End of file
#