Skip to content

Commit

Permalink
add sig and hash test vectors with expected outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
kwantam committed Jun 4, 2019
1 parent 6130de4 commit 162f102
Show file tree
Hide file tree
Showing 60 changed files with 3,115 additions and 58 deletions.
4 changes: 2 additions & 2 deletions python-impl/bls_sig_g1.py
Expand Up @@ -32,6 +32,6 @@ def verify(pk, sig, msg, ciphersuite):
def main():
opts = get_cmdline_options()
ver_fn = verify if opts.verify else None
for (msg, sk) in opts.sig_inputs:
print_tv_sig(sk, msg, g1suite, sign, keygen, print_g2_hex, print_g1_hex, ver_fn)
for sig_in in opts.test_inputs:
print_tv_sig(sig_in, g1suite, sign, keygen, print_g2_hex, print_g1_hex, ver_fn)
main()
4 changes: 2 additions & 2 deletions python-impl/bls_sig_g2.py
Expand Up @@ -32,6 +32,6 @@ def verify(pk, sig, msg, ciphersuite):
def main():
opts = get_cmdline_options()
ver_fn = verify if opts.verify else None
for (msg, sk) in opts.sig_inputs:
print_tv_sig(sk, msg, g2suite, sign, keygen, print_g1_hex, print_g2_hex, ver_fn)
for sig_in in opts.test_inputs:
print_tv_sig(sig_in, g2suite, sign, keygen, print_g1_hex, print_g2_hex, ver_fn)
main()
27 changes: 27 additions & 0 deletions python-impl/fields.py
Expand Up @@ -11,12 +11,25 @@
# * Some unneeded functionality was removed and some pylint errors were fixed.
# * added trivial __reversed__ method to Fq to support generic sgn0 impl
# * q -> p in frob_coeffs for consistency with the rest of this library
# * moved sgn0 and sqrt_F2 into this file
#
# Changes (C) 2019 Riad S. Wahby <rsw@cs.stanford.edu>

from copy import deepcopy
from consts import p

# "sign" of x: returns -1 if x is the lexically larger of x and -1 * x, else returns 1
def sgn0(x):
thresh = (p - 1) // 2
sign = 0
for xi in reversed(x):
if xi > thresh:
sign = -1 if sign == 0 else sign
elif xi > 0:
sign = 1 if sign == 0 else sign
sign = 1 if sign == 0 else sign
return sign

class Fq(int):
"""
Represents an element of a finite field mod a prime q.
Expand Down Expand Up @@ -345,6 +358,20 @@ def mul_by_nonresidue(self):
a, b = self
return Fq2(self.Q, a - b, a + b)

# roots of unity, used for computing square roots in Fq2
rv1 = 0x6af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09
roots_of_unity = (Fq2(p, 1, 0), Fq2(p, 0, 1), Fq2(p, rv1, rv1), Fq2(p, rv1, p - rv1))
del rv1

# sqrt function -- returns None when input is nonsquare
def sqrt_F2(val):
sqrt_cand = pow(val, (p ** 2 + 7) // 16)
ret = None
for root in roots_of_unity:
tmp = sqrt_cand * root
ret = tmp if pow(tmp, 2) == val else ret
return ret

class Fq6(FieldExtBase):
# Fq6 is constructed as Fq2(v) / (v^3 - j) where j = u + 1
extension = 6
Expand Down
18 changes: 3 additions & 15 deletions python-impl/opt_swu_g1.py
Expand Up @@ -6,7 +6,7 @@

from consts import g1suite, p
from curve_ops import clear_h, eval_iso, from_jacobian, point_add
from fields import Fq
from fields import Fq, sgn0
from hash_to_field import Hp
from util import get_cmdline_options, print_g1_hex, print_tv_hash

Expand All @@ -17,18 +17,6 @@
EllP_a = Fq(p, 0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d)
EllP_b = Fq(p, 0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0)

# "sign" of x: returns -1 if x is the lexically larger of x and -1 * x, else returns 1
def sgn0(x):
thresh = (p - 1) // 2
sign = 0
for xi in reversed(x):
if xi > thresh:
sign = -1 if sign == 0 else sign
elif xi > 0:
sign = 1 if sign == 0 else sign
sign = 1 if sign == 0 else sign
return sign

###
## Simplified SWU map for Ell1'
###
Expand Down Expand Up @@ -178,7 +166,7 @@ def main():
if opts.run_tests:
run_tests()
else:
for (msg, _) in opts.sig_inputs:
print_tv_hash(msg, g1suite, map2curve_osswu, print_g1_hex)
for hash_in in opts.test_inputs:
print_tv_hash(hash_in, g1suite, map2curve_osswu, print_g1_hex)

main()
21 changes: 3 additions & 18 deletions python-impl/opt_swu_g2.py
Expand Up @@ -6,9 +6,8 @@

from consts import g2suite, p
from curve_ops import clear_h2, eval_iso, from_jacobian, point_add
from fields import Fq2
from fields import Fq2, sgn0, roots_of_unity
from hash_to_field import Hp2
from opt_swu_g1 import sgn0
from util import get_cmdline_options, print_g2_hex, print_tv_hash

# distinguished non-square in Fp2 for SWU map
Expand All @@ -18,20 +17,6 @@
Ell2p_a = Fq2(p, 0, 240)
Ell2p_b = Fq2(p, 1012, 1012)

# roots of unity, used for computing square roots
rv1 = 0x6af0e0437ff400b6831e36d6bd17ffe48395dabc2d3435e77f76e17009241c5ee67992f72ec05f4c81084fbede3cc09
roots_of_unity = (Fq2(p, 1, 0), Fq2(p, 0, 1), Fq2(p, rv1, rv1), Fq2(p, rv1, p - rv1))
del rv1

# sqrt function -- returns None when input is nonsquare
def sqrt_F2(val):
sqrt_cand = pow(val, (p ** 2 + 7) // 16)
ret = None
for root in roots_of_unity:
tmp = sqrt_cand * root
ret = tmp if pow(tmp, 2) == val else ret
return ret

# eta values, used for computing sqrt(g(X1(t)))
ev1 = 0x2c4a7244a026bd3e305cc456ad9e235ed85f8b53954258ec8186bb3d4eccef7c4ee7b8d4b9e063a6c88d0aa3e03ba01
ev2 = 0x85fa8cd9105715e641892a0f9a4bb2912b58b8d32f26594c60679cc7973076dc6638358daf3514d6426a813ae01f51a
Expand Down Expand Up @@ -183,7 +168,7 @@ def main():
if opts.run_tests:
run_tests()
else:
for (msg, _) in opts.sig_inputs:
print_tv_hash(msg, g2suite, map2curve_osswu2, print_g2_hex)
for hash_in in opts.test_inputs:
print_tv_hash(hash_in, g2suite, map2curve_osswu2, print_g2_hex)

main()
7 changes: 4 additions & 3 deletions python-impl/serdes.py
Expand Up @@ -13,9 +13,7 @@

from consts import p
from curve_ops import from_jacobian, point_eq
from fields import Fq, Fq2
from opt_swu_g1 import sgn0, opt_swu_map
from opt_swu_g2 import sqrt_F2, opt_swu2_map
from fields import Fq, Fq2, sgn0, sqrt_F2

F1_one = Fq.one(p)
F1_zero = Fq.zero(p)
Expand Down Expand Up @@ -223,6 +221,9 @@ def _deserialize_ell2(data, tag):
import binascii
import random

from opt_swu_g1 import opt_swu_map
from opt_swu_g2 import opt_swu2_map

invalid_inputs = [
# infty points, too short
"c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
Expand Down
51 changes: 36 additions & 15 deletions python-impl/util.py
Expand Up @@ -12,16 +12,15 @@

from consts import q
from curve_ops import g1gen, g2gen, from_jacobian
from serdes import serialize, deserialize, SerError, DeserError

class Options(object):
run_tests = False
sig_inputs = None
ver_inputs = None
test_inputs = None
verify = False

def __init__(self):
self.sig_inputs = []
self.ver_inputs = []
self.test_inputs = []

def _read_test_file(filename):
ret = []
Expand All @@ -36,7 +35,7 @@ def get_cmdline_options():

# process cmdline args with getopt
try:
(opts, args) = getopt.gnu_getopt(sys.argv[1:], "k:T:tvV:")
(opts, args) = getopt.gnu_getopt(sys.argv[1:], "k:T:tv")

except getopt.GetoptError as err:
print("Usage: %s [-t]" % sys.argv[0])
Expand All @@ -48,10 +47,7 @@ def get_cmdline_options():
sk = os.fsencode(arg)

elif opt == "-T":
ret.sig_inputs += _read_test_file(arg)

elif opt == "-V":
ret.ver_inputs += _read_test_file(arg)
ret.test_inputs += _read_test_file(arg)

elif opt == "-t":
ret.run_tests = True
Expand All @@ -63,9 +59,9 @@ def get_cmdline_options():
raise RuntimeError("got unexpected option %s from getopt" % opt)

# build up return value: (msg, sk) tuples from cmdline and test files
ret.sig_inputs += [ (os.fsencode(arg), sk) for arg in args ]
if not ret.sig_inputs:
ret.sig_inputs = [ (msg_dflt, sk) ]
ret.test_inputs += [ (os.fsencode(arg), sk) for arg in args ]
if not ret.test_inputs:
ret.test_inputs = [ (msg_dflt, sk) ]

return ret

Expand Down Expand Up @@ -113,9 +109,21 @@ def print_value(iv, indent=8, skip_first=False):
line_length += len(out_str) + 1
sys.stdout.write("\n")

def print_tv_hash(msg, ciphersuite, hash_fn, print_pt_fn):
def print_tv_hash(hash_in, ciphersuite, hash_fn, print_pt_fn):
if len(hash_in) > 2:
(msg, _, hash_expect) = hash_in[:3]
else:
msg = hash_in[0]
hash_expect = None
# hash to point
P = hash_fn(prepare_msg(msg, ciphersuite))

if hash_expect is not None:
if serialize(P) != hash_expect:
raise SerError("serializing P did not give hash_expect")
if from_jacobian(deserialize(hash_expect)) != from_jacobian(P):
raise DeserError("deserializing hash_expect did not give P")

print("=============== begin hash test vector ==================")

print("ciphersuite: 0x%x" % ciphersuite)
Expand All @@ -128,11 +136,24 @@ def print_tv_hash(msg, ciphersuite, hash_fn, print_pt_fn):

print("=============== end hash test vector ==================")

def print_tv_sig(sk, msg, ciphersuite, sign_fn, keygen_fn, print_pk_fn, print_sig_fn, ver_fn):
def print_tv_sig(sig_in, ciphersuite, sign_fn, keygen_fn, print_pk_fn, print_sig_fn, ver_fn):
if len(sig_in) > 2:
(msg, sk, sig_expect) = sig_in[:3]
else:
(msg, sk) = sig_in
sig_expect = None
# generate key and signature
(x_prime, pk) = keygen_fn(sk)
sig = sign_fn(x_prime, msg, ciphersuite)
assert ver_fn is None or ver_fn(pk, sig, msg, ciphersuite)

if sig_expect is not None:
if serialize(sig) != sig_expect:
raise SerError("serializing sig did not give sig_expect")
if from_jacobian(deserialize(sig_expect)) != from_jacobian(sig):
raise DeserError("deserializing sig_expect did not give sig")

if ver_fn is not None and not ver_fn(pk, sig, msg, ciphersuite):
raise RuntimeError("verifying generated signature failed")

# output the test vector
print("================== begin test vector ====================")
Expand Down
25 changes: 22 additions & 3 deletions test-vectors/README.md
@@ -1,11 +1,11 @@
The files in this directory comprises test inputs, one per line.

Each line is a space-separated tuple (msg, key).
Each line is a space-separated tuple (msg, sk).

msg and key should be interpreted as hex-encoded octet strings, wherein each
msg and sk should be interpreted as hex-encoded octet strings, wherein each
pair of hex characters represents one byte.

Equivalently, msg and key can be interpreted as hexadecimal integers and then
Equivalently, msg and sk can be interpreted as hexadecimal integers and then
converted to octet strings using I2OSP (RFC 8017).

In Python, the following code can be used to load the file:
Expand All @@ -22,3 +22,22 @@ with open("fips_186_3", "r") as vec_file:
These files are extracted from the NIST CAVP ECDSA test vectors, available from
https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Digital-Signatures
https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss/186-3ecdsatestvectors.zip

## `sig_g1`, `sig_g2` subdirectories

The files in these subdirs correspond to the above, except that the lines in each
file are space-separated tuples (msg, sk, sig).

- In `sig_g1`, sig is the signature on msg under sk in the G1 group.

- In `sig_g2`, the signature is in the G2 group instead.

## `hash_g1`, `hash_g2` subdirectories

The files in these subdirs correspond to the above, except that the lines in each
file are space-separated tuples (msg, "00", P). The literal string "00" helps
to avoid confusion with files containing (msg, sk).

- In `hash_g1`, P is the hash of msg to the G1 group.

- In `hash_g2`, P is the hash of msg to the G2 group.

0 comments on commit 162f102

Please sign in to comment.