aboutsummaryrefslogblamecommitdiff
path: root/test_vectors/format_test_vectors.py
blob: e6e04c5d81dc7b6325867cbfc3db387c1c3c5237 (plain) (tree)
1
2
3
4
5
6
7





                                            
                                       






































                                                                            

                                      






                                 
                                              

                          
                                              
 


                        






                                                             














                                                                          








                                                                              
        


                        







                                                                     
 


                                   
 


                                   
 
                                             
                                                                                                                  
 




                                                                                                                  
 
                                                                                                                  

                                                                                                                  










                                                                                                                  















































                                                                                      
                                          
 


































                                                                                      





























































































                                                                                                                        
                             


                                                           
                             

















                                                           

                                                 


                                                                       
                                                                       

                                      
                                 

                                   

                                                           

                                            

                                                                               

                                          
















                                                                               











                                                                                          


                                                                     



                                                                     








                                                                             


                      
                      






                                           
#
# format_test_vectors.py
# ------------------------------------------
# Formats test vectors 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.
#

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

#
# imports
#
import sys
import subprocess
from fastecdsa import keys, curve
from fastecdsa.curve import P256, P384
from fastecdsa.point import Point

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

# the base point for p-256
P256_BASE = keys.get_public_key(1, curve.P256)

# the base point for p-384
P384_BASE = keys.get_public_key(1, curve.P384)

#
# format one test vector
#
def format_c_header(	f, curve,
						da, qax, qay,
						db, qbx, qby,
						sx, sy,
						hx, hy,
						qa2x, qa2y,
						qb2x, qb2y):

	if curve == CURVE_P256: curve_str = "P_256"
	if curve == CURVE_P384: curve_str = "P_384"
	
		# write all numbers in vector
	format_c_array(f, da,  "#define " + curve_str + "_DA"   + " \\\n")
	format_c_array(f, qax, "#define " + curve_str + "_QA_X" + " \\\n")
	format_c_array(f, qay, "#define " + curve_str + "_QA_Y" + " \\\n")
	
	format_c_array(f, db,  "#define " + curve_str + "_DB"   + " \\\n")
	format_c_array(f, qbx, "#define " + curve_str + "_QB_X" + " \\\n")
	format_c_array(f, qby, "#define " + curve_str + "_QB_Y" + " \\\n")

	format_c_array(f, sx,  "#define " + curve_str + "_S_X"  + " \\\n")
	format_c_array(f, sy,  "#define " + curve_str + "_S_Y"  + " \\\n")

	format_c_array(f, hx,  "#define " + curve_str + "_H_X"  + " \\\n")
	format_c_array(f, hy,  "#define " + curve_str + "_H_Y"  + " \\\n")

	format_c_array(f, qa2x,  "#define " + curve_str + "_QA2_X"  + " \\\n")
	format_c_array(f, qa2y,  "#define " + curve_str + "_QA2_Y"  + " \\\n")

	format_c_array(f, qb2x,  "#define " + curve_str + "_QB2_X"  + " \\\n")
	format_c_array(f, qb2y,  "#define " + curve_str + "_QB2_Y"  + " \\\n")
	
#
# format one test vector
#
def format_verilog_include(	f, curve, n,
							da, qax, qay,
							db, qbx, qby,
							sx, sy,
							gx, gy,
							hx, hy,
							qa2x, qa2y,
							qb2x, qb2y):

	if curve == CURVE_P256:
		curve_str = "P_256"
		msb_index = "255"

	if curve == CURVE_P384:
		curve_str = "P_384"
		msb_index = "383"

		# write all numbers in vector
	format_verilog_concatenation(f, n,    "localparam [" + msb_index + ":0] " + curve_str + "_N"     + " =\n")

	format_verilog_concatenation(f, da,   "localparam [" + msb_index + ":0] " + curve_str + "_DA"    + " =\n")
	format_verilog_concatenation(f, qax,  "localparam [" + msb_index + ":0] " + curve_str + "_QA_X"  + " =\n")
	format_verilog_concatenation(f, qay,  "localparam [" + msb_index + ":0] " + curve_str + "_QA_Y"  + " =\n")
	format_verilog_concatenation(f, qa2x, "localparam [" + msb_index + ":0] " + curve_str + "_QA2_X" + " =\n")
	format_verilog_concatenation(f, qa2y, "localparam [" + msb_index + ":0] " + curve_str + "_QA2_Y" + " =\n")

	format_verilog_concatenation(f, db,   "localparam [" + msb_index + ":0] " + curve_str + "_DB"    + " =\n")
	format_verilog_concatenation(f, qbx,  "localparam [" + msb_index + ":0] " + curve_str + "_QB_X"  + " =\n")
	format_verilog_concatenation(f, qby,  "localparam [" + msb_index + ":0] " + curve_str + "_QB_Y"  + " =\n")
	format_verilog_concatenation(f, qb2x, "localparam [" + msb_index + ":0] " + curve_str + "_QB2_X" + " =\n")
	format_verilog_concatenation(f, qb2y, "localparam [" + msb_index + ":0] " + curve_str + "_QB2_Y" + " =\n")

	format_verilog_concatenation(f, sx,   "localparam [" + msb_index + ":0] " + curve_str + "_S_X"   + " =\n")
	format_verilog_concatenation(f, sy,   "localparam [" + msb_index + ":0] " + curve_str + "_S_Y"   + " =\n")

	format_verilog_concatenation(f, gx,   "localparam [" + msb_index + ":0] " + curve_str + "_G_X"   + " =\n")
	format_verilog_concatenation(f, gy,   "localparam [" + msb_index + ":0] " + curve_str + "_G_Y"   + " =\n")

	format_verilog_concatenation(f, hx,   "localparam [" + msb_index + ":0] " + curve_str + "_H_X"   + " =\n")
	format_verilog_concatenation(f, hy,   "localparam [" + msb_index + ":0] " + curve_str + "_H_Y"   + " =\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(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 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)
	
		# another sanity check (make sure, that Q is actually d * G)
	if curve == CURVE_P256:
		G = P256_BASE
		Q = Point(key_pub_x, key_pub_y, curve=P256)

	if curve == CURVE_P384:
		G = P384_BASE
		Q = Point(key_pub_x, key_pub_y, curve=P384)
	
		# multiply using fastecdsa
	R = key_priv * G

	if R.x != Q.x: sys.exit()
	if R.y != Q.y: sys.exit()
		
		# done
	return key_priv, key_pub_x, key_pub_y

	
if __name__ == "__main__":

		# list of curves to process
	curves = [CURVE_P256, CURVE_P384]

		# open output files
	file_h = open('ecdh_test_vectors.h', 'w')
	file_v = open('ecdh_test_vectors.v', '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("alice", next_curve)
		db, qbx, qby = get_key("bob",   next_curve)
		
			# Alice's public key
		if (next_curve == CURVE_P256): QA = Point(qax, qay, curve=P256)
		if (next_curve == CURVE_P384): QA = Point(qax, qay, curve=P384)

			# Bob's public key
		if (next_curve == CURVE_P256): QB = Point(qbx, qby, curve=P256)
		if (next_curve == CURVE_P384): QB = Point(qbx, qby, curve=P384)
		
			# the base point
		if (next_curve == CURVE_P256): G = P256_BASE
		if (next_curve == CURVE_P384): G = P384_BASE
		
			# double of the base point
		H = 2 * G
		
			# doubles of QA and QB
		QA2 = 2 * QA
		QB2 = 2 * QB
		
			# order of the base point
		if (next_curve == CURVE_P256): n = curve.P256.q
		if (next_curve == CURVE_P384): n = curve.P384.q
		
			# we derive the shared secret two different ways (from Alice's and
			# from Bob's perspective, they must be identical of course
		QAB = da * QB	# Alice's secret
		QBA = db * QA	# Bob's secret
		
		if (QAB.x != QBA.x): sys.exit()
		if (QBA.y != QAB.y): sys.exit()
		
		print("Derived shared secret.");

			# format numbers and write to file
		format_c_header(	file_h, next_curve,
							da, qax, qay,
							db, qbx, qby,
							QAB.x, QBA.y,
							H.x, H.y,
							QA2.x, QA2.y,
							QB2.x, QB2.y)
							
		format_verilog_include(	file_v, next_curve, n,
								da, qax, qay,
								db, qbx, qby,
								QAB.x, QBA.y,
								G.x, G.y,
								H.x, H.y,
								QA2.x, QA2.y,
								QB2.x, QB2.y)

		# done
	file_h.close()
	file_v.close()
	
		# everything went just fine
	print("Test vectors formatted.")

#
# End of file
#