Skip to content

Commit

Permalink
Add initial Sage reference implementation (#99)
Browse files Browse the repository at this point in the history
* OPRF P-384.

* Add more ciphersuites.

Co-authored-by: Christopher Wood <[email protected]>
  • Loading branch information
chris-wood and Christopher Wood authored Jun 16, 2020
1 parent 2afa9f9 commit 2ce8851
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "poc/h2c"]
path = poc/h2c
url = [email protected]:cfrg/draft-irtf-cfrg-hash-to-curve.git
31 changes: 31 additions & 0 deletions poc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
h2c
clear_h_bls12381g2.sage
common.sage
curves.sage
ell2_generic.sage
ell2_opt_3mod4.sage
ell2_opt_5mod8.sage
ell2edw_generic.sage
generic_map.sage
h2c_suite.sage
hash_to_field.py
iso_values.sage
map_check.sage
sagelib/
sswu_generic.sage
sswu_opt_3mod4.sage
sswu_opt_5mod8.sage
sswu_opt_9mod16.sage
suite_25519.sage
suite_448.sage
suite_bls12381g1.sage
suite_bls12381g2.sage
suite_p256.sage
suite_p384.sage
suite_p521.sage
suite_secp256k1.sage
svdw_generic.sage
test.sage
test_vectors.sage
z_selection.sage
z_values.sage
34 changes: 34 additions & 0 deletions poc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
SAGEFILES := $(basename $(notdir $(wildcard *.sage)))
PYFILES := $(addprefix sagelib/, $(addsuffix .py,$(SAGEFILES)))
.PRECIOUS: $(PYFILES)

.PHONY: pyfiles
pyfiles: sagelib/__init__.py $(PYFILES)

sagelib/__init__.py:
mkdir -p sagelib
echo pass > sagelib/__init__.py

sagelib/%.py: %.sage
@echo "Parsing $<"
@sage --preparse $<
@mv $<.py $@

setup:
cp h2c/poc/hash_to_field.py .
cp h2c/poc/*.sage .

test: pyfiles
sage test.sage

vectors: pyfiles
@mkdir -p vectors ascii
sage test_vectors.sage

.PHONY: clean
clean:
rm -rf sagelib *.pyc *.sage.py *.log __pycache__

.PHONY: distclean
distclean: clean
rm -rf vectors ascii
1 change: 1 addition & 0 deletions poc/h2c
Submodule h2c added at ead9c9
241 changes: 241 additions & 0 deletions poc/oprf.sage
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#!/usr/bin/sage
# vim: syntax=python

import sys
import json
import hashlib
import binascii

from hash_to_field import I2OSP

try:
from sagelib.suite_p256 import p256_sswu_ro, p256_order, p256_p, p256_F, p256_A, p256_B
from sagelib.suite_p384 import p384_sswu_ro, p384_order, p384_p, p384_F, p384_A, p384_B
from sagelib.suite_p521 import p521_sswu_ro, p521_order, p521_p, p521_F, p521_A, p521_B
except ImportError as e:
sys.exit("Error loading preprocessed sage files. Try running `make setup && make clean pyfiles`. Full error: " + e)

if sys.version_info[0] == 3:
xrange = range
_as_bytes = lambda x: x if isinstance(x, bytes) else bytes(x, "utf-8")
_strxor = lambda str1, str2: bytes( s1 ^ s2 for (s1, s2) in zip(str1, str2) )
else:
_as_bytes = lambda x: x
_strxor = lambda str1, str2: ''.join( chr(ord(s1) ^ ord(s2)) for (s1, s2) in zip(str1, str2) )

def to_hex(octet_string):
if isinstance(octet_string, str):
return "".join("{:02x}".format(ord(c)) for c in octet_string)
assert isinstance(octet_string, bytes)
return "0x" + "".join("{:02x}".format(c) for c in octet_string)


class Group(object):
def __init__(self, name):
self.name = name

def order(self):
return 0

def generator(self):
return 0

def random_scalar(self):
return None

def identity(self):
return 0

def serialize(self, element):
return None

def deserialize(self, encoded):
return None

def hash_to_group(self, encoded):
return None

def __str__(self):
return self.name

class GroupNISTCurve(Group):
def __init__(self, name, suite, F, A, B, p, order, gx, gy):
Group.__init__(self, name)
self.F = F
EC = EllipticCurve(F, [F(A), F(B)])
self.curve = EC
self.gx = gx
self.gy = gy
self.p = p
self.order = order
self.h2c_suite = suite
self.G = EC(F(gx), F(gy))

def suite_name(self):
return self.name

def order(self):
return self.order

def generator(self):
return self.G

def random_scalar(self):
return self.F.random_element()

def identity(self):
return self.curve.random_element() * self.order()

def serialize(self, element):
x, y = element[0], element[1]
L = int(((log(self.p, 2) + 8) / 8).n())
return I2OSP(4, 1) + I2OSP(x, L) + I2OSP(y, L)

def deserialize(self, encoded):
# 0x04 || x || y
assert(encoded[0] == 0x04)
assert(len(encoded) % 2 != 0)
element_length = (len(encoded) - 1) / 2
x = OS2IP(encoded[1:element_length+1])
y = OS2IP(encoded[1+element_length:])
return self.EC(F(x), F(y))

def hash_to_group(self, msg, dst):
self.h2c_suite.dst = dst
return self.h2c_suite(msg)

class GroupP256(GroupNISTCurve):
def __init__(self):
# See FIPS 186-3, section D.2.3
gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
GroupNISTCurve.__init__(self, "P256_XMD:SHA-512_SSWU_RO_", p256_sswu_ro, p256_F, p256_A, p256_B, p256_p, p256_order, gx, gy)

class GroupP384(GroupNISTCurve):
def __init__(self):
# See FIPS 186-3, section D.2.4
gx = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7
gy = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f
GroupNISTCurve.__init__(self, "P384_XMD:SHA-512_SSWU_RO_", p384_sswu_ro, p384_F, p384_A, p384_B, p384_p, p384_order, gx, gy)

class GroupP521(GroupNISTCurve):
def __init__(self):
# See FIPS 186-3, section D.2.5
gx = 0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66
gy = 0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650
GroupNISTCurve.__init__(self, "P521_XMD:SHA-512_SSWU_RO_", p521_sswu_ro, p521_F, p521_A, p521_B, p521_p, p521_order, gx, gy)


class Ciphersuite(object):
def __init__(self, name, group, H1):
self.name = name
self.group = group
self.H1 = H1

def __str__(self):
return self.name

class VerifiableCiphersuite(Ciphersuite):
def __init__(self, name, group, H1, H2, H3):
Ciphersuite.__init__(self, name, group, H1)
self.H2 = H2
self.H3 = H3


class Client(object):
def __init__(self, suite):
self.suite = suite
self.dst = _as_bytes("RFCXXXX-VOPRF-" + self.suite.group.suite_name())

def blind(self, x):
r = ZZ(self.suite.group.random_scalar())
P = self.suite.group.hash_to_group(x, self.dst)
X = r * P
return r, X, P

def unblind(self, N, r):
r_inv = inverse_mod(r, self.suite.group.G.order())
y = r_inv * N
return y

def finalize(self, x, y, aux):
h = self.suite.H1()

finalize_dst = _as_bytes("RFCXXXX-Finalize")
encoded_point = self.suite.group.serialize(y)

h.update(I2OSP(len(finalize_dst), 2))
h.update(finalize_dst)
h.update(I2OSP(len(x), 2))
h.update(x)
h.update(I2OSP(len(encoded_point), 2))
h.update(encoded_point)

return h.digest()

class Server(object):
def __init__(self, suite):
self.suite = suite
self.k = ZZ(self.suite.group.random_scalar())

def set_key(self, k):
self.k = k

def evaluate(self, element):
return self.k * element


class Protocol(object):
def __init__(self):
self.inputs = map(lambda h : _as_bytes(bytearray.fromhex(h).decode()), ['00', '01', '02'])

def run_vector(self, vector):
raise Exception("Not implemented")

def run(self, client, server, aux):
assert(client.suite.group == server.suite.group)
group = client.suite.group

vectors = []
for x in self.inputs:
r, R, P = client.blind(x)
T = server.evaluate(R)
Z = client.unblind(T, r)
y = client.finalize(x, Z, aux)

vector = {}
vector["x"] = to_hex(x)
vector["P"] = to_hex(group.serialize(P))
vector["R"] = to_hex(group.serialize(R))
vector["T"] = to_hex(group.serialize(T))
vector["Z"] = to_hex(group.serialize(Z))
vector["y"] = to_hex(y)
vectors.append(vector)

vector = {}
vector["k"] = hex(server.k)
vector["aux"] = aux
vector["suite"] = client.suite.name
vector["vectors"] = vectors

return vector

ciphersuites = {
Ciphersuite("OPRF-P256-HKDF-SHA512-SSWU-RO", GroupP256(), hashlib.sha512),
Ciphersuite("OPRF-P384-HKDF-SHA512-SSWU-RO", GroupP384(), hashlib.sha512),
Ciphersuite("OPRF-P521-HKDF-SHA512-SSWU-RO", GroupP521(), hashlib.sha512),
}

def main():
vectors = {}
for suite in ciphersuites:
client = Client(suite)
server = Server(suite)
protocol = Protocol()
vectors[suite.name] = protocol.run(client, server, "test auxiliary information")

print(json.dumps(vectors))

if __name__ == "__main__":
# fixed_voprf()
main()

0 comments on commit 2ce8851

Please sign in to comment.