Skip to content

Commit

Permalink
Python code for DAC using mercurial sigs
Browse files Browse the repository at this point in the history
  • Loading branch information
burkh4rt committed Aug 7, 2020
1 parent dfd8591 commit e4d8c0c
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 0 deletions.
65 changes: 65 additions & 0 deletions python/delegatable_anon_cred_scheme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from mercurial_signature_scheme import MercurialSignatureScheme, MercurialSignatureDual


class DelegatableAnonCredScheme:
def __init__(self, ell):
self.MSS1 = MercurialSignatureScheme()
self.MSS2 = MercurialSignatureDual()
self.ell = ell
self.pk0, self.sk0 = self.MSS2.KeyGen(self.ell)
self.nym0 = (self.pk0, None)

def KeyGen(self):
pk_even, sk_even = self.MSS2.KeyGen(self.ell)
pk_odd, sk_odd = self.MSS1.KeyGen(self.ell)
return (pk_even, sk_even), (pk_odd, sk_odd)

def NymGen(self, pk_even, sk_even, pk_odd, sk_odd):
rho_even = self.MSS2.RandomZp()
sk_even = self.MSS2.ConvertSK(sk_even, rho_even)
nym_even = self.MSS2.ConvertPK(pk_even, rho_even)
rho_odd = self.MSS1.RandomZp()
sk_odd = self.MSS1.ConvertSK(sk_odd, rho_odd)
nym_odd = self.MSS1.ConvertPK(pk_odd, rho_odd)
return (nym_even, sk_even), (nym_odd, sk_odd)

def IssueFirst(self, nym1):
sig1 = self.MSS2.Sign(self.sk0, nym1)
return [nym1], [sig1]

def IssueNext(self, cred_chain, new_nym, sk):
nym_list, sig_list = cred_chain
assert len(nym_list) == len(sig_list)
rho = self.MSS2.RandomZp()
nym_list[0], sig_list[0] = self.MSS2.ChangeRep(
self.pk0, nym_list[0], sig_list[0], rho
)
assert self.MSS2.Verify(self.pk0, nym_list[0], sig_list[0])
for i in range(len(nym_list) - 1):
# Note: MSS1 & MSS2 share the same functions RandomZp, ChangeRep, & ConvertSig
MSS = self.MSS1 if i % 2 == 0 else self.MSS2
sig_tilde = MSS.ConvertSig(
nym_list[i], nym_list[i + 1], sig_list[i + 1], rho
)
rho = MSS.RandomZp()
nym_list[i + 1], sig_list[i + 1] = MSS.ChangeRep(
nym_list[i], nym_list[i + 1], sig_tilde, rho
)
assert MSS.Verify(nym_list[i], nym_list[i + 1], sig_list[i + 1])
nym_list.append(new_nym)
MSS = self.MSS1 if len(nym_list) % 2 == 0 else self.MSS2
sk = MSS.ConvertSK(sk, rho)
sig_list.append(MSS.Sign(sk, new_nym))
assert MSS.Verify(nym_list[-2], nym_list[-1], sig_list[-1])
return nym_list, sig_list

def VerifyChain(self, cred_chain):
nym_list, sig_list = cred_chain
assert len(nym_list) == len(sig_list)
if not self.MSS2.Verify(self.pk0, nym_list[0], sig_list[0]):
return False
for i in range(len(nym_list) - 1):
MSS = self.MSS1 if i % 2 == 0 else self.MSS2
if not MSS.Verify(nym_list[i], nym_list[i + 1], sig_list[i + 1]):
return False
return True
120 changes: 120 additions & 0 deletions python/mercurial_signature_scheme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import functools
import hashlib

from miracl_core_py_bn254 import big
from miracl_core_py_bn254 import curve
from miracl_core_py_bn254 import ecp
from miracl_core_py_bn254 import ecp2
from miracl_core_py_bn254 import fp12
from miracl_core_py_bn254 import pair


class MercurialSignatureScheme:
def __init__(self):
self.G1 = ecp.ECp()
self.G2 = ecp2.ECp2()
self.GT = fp12.Fp12()
self.P = ecp.generator().copy()
self.Phat = ecp2.generator().copy()
self.e = pair.e
self.curve = curve

def KeyGen(self, ell):
sk = []
pk = []
for _ in range(ell):
x = self.RandomZp()
w = x * self.Phat
sk.append(x)
pk.append(w)
return pk, sk

def Sign(self, sk, M):
y = self.RandomZp()
Z = y * functools.reduce(
lambda a, b: a.add(b), [xi * Mi for xi, Mi in zip(sk, M)]
)
Y = big.invmodp(y, self.curve.r) * self.P
Yhat = big.invmodp(y, self.curve.r) * self.Phat
return Z, Y, Yhat

def Verify(self, pk, M, sigma):
Z, Y, Yhat = sigma
q1 = functools.reduce(
lambda a, b: a * b, [self.e(Xi, Mi) for Xi, Mi in zip(pk, M)]
)
return q1 == self.e(Yhat, Z) and self.e(self.Phat, Y) == self.e(Yhat, self.P)

def ConvertSK(self, sk, rho):
return [rho * xi for xi in sk]

def ConvertPK(self, pk, rho):
return [rho * Xi for Xi in pk]

def ConvertSig(self, pk, M, sigma, rho):
Z, Y, Yhat = sigma
psi = self.RandomZp()
return (
psi * rho * Z,
big.invmodp(psi, self.curve.r) * Y,
big.invmodp(psi, self.curve.r) * Yhat,
)

def ChangeRep(self, pk, M, sigma, mu):
Z, Y, Yhat = sigma
psi = self.RandomZp()
M0 = [mu * m for m in M]
sigma0 = (
psi * mu * Z,
big.invmodp(psi, self.curve.r) * Y,
big.invmodp(psi, self.curve.r) * Yhat,
)
return M0, sigma0

@staticmethod
def HashMessage(m):
h = hashlib.shake_256()
h.update(bytes(m, "utf-8"))
hm = big.from_bytes(h.digest(curve.EFS))
HM = ecp.ECp()
while not HM.set(hm):
hm = hm + 1
HM = curve.CurveCof * HM
return HM

@staticmethod
def RandomZp():
return big.rand(curve.r)


class MercurialSignatureDual(MercurialSignatureScheme):
def KeyGen(self, ell):
sk = []
pk = []
for _ in range(ell):
x = self.RandomZp()
w = x * self.P
sk.append(x)
pk.append(w)
return pk, sk

def Sign(self, sk, M):
y = self.RandomZp()
Z = y * functools.reduce(
lambda a, b: a.add(b), [Xi * Mi for Xi, Mi in zip(sk, M)]
)
Y = big.invmodp(y, self.curve.r) * self.Phat
Yhat = big.invmodp(y, self.curve.r) * self.P
return Z, Y, Yhat

def Verify(self, pk, M, sigma):
Z, Y, Yhat = sigma
q1 = functools.reduce(
lambda a, b: a * b, [self.e(Mi, Xi) for Xi, Mi in zip(pk, M)]
)
return q1 == self.e(Z, Yhat) and self.e(Y, self.P) == self.e(self.Phat, Yhat)

@staticmethod
def HashMessage(m):
# not a real hash but sufficient for testing purposes
return big.rand(curve.r) * ecp2.generator().copy()
15 changes: 15 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.black]
line-length = 88
py36 = false
exclude = '''
/(
\.git
| \.idea
| miracl_core_py_bn254
| miracl_core_cpp_bn254
| miracl_core_c_bn254
# The following are specific to Black, you probably don't want those.
| blib2to3
| tests/data
)/
'''
58 changes: 58 additions & 0 deletions python/test_delegatable_anon_cred_scheme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import unittest

from delegatable_anon_cred_scheme import DelegatableAnonCredScheme


class TestMercurialSignatureScheme(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.DAC1 = DelegatableAnonCredScheme(2)
cls.DAC2 = DelegatableAnonCredScheme(7)
cls.DAC3 = DelegatableAnonCredScheme(9)

def test_chain(self):
for DAC in [self.DAC1, self.DAC2, self.DAC3]:

# user 1 generates keys, nyms, & gets on the credential chain
even_keys1, odd_keys1 = DAC.KeyGen()
(nym_even1, sk_even1), (nym_odd1, sk_odd1) = DAC.NymGen(
*even_keys1, *odd_keys1
)
cred_chain = DAC.IssueFirst(nym_odd1)
self.assertTrue(DAC.VerifyChain(cred_chain), "user 1 checks out")

# user 2 generates keys, nyms, & gets on the credential chain
even_keys2, odd_keys2 = DAC.KeyGen()
(nym_even2, sk_even2), (nym_odd2, sk_odd2) = DAC.NymGen(
*even_keys2, *odd_keys2
)
cred_chain = DAC.IssueNext(cred_chain, nym_even2, sk_odd1)
self.assertTrue(DAC.VerifyChain(cred_chain), "user 2 is a-ok")

# user 3 generates keys, nyms, & gets on the credential chain
even_keys3, odd_keys3 = DAC.KeyGen()
(nym_even3, sk_even3), (nym_odd3, sk_odd3) = DAC.NymGen(
*even_keys3, *odd_keys3
)
cred_chain = DAC.IssueNext(cred_chain, nym_odd3, sk_even2)
self.assertTrue(DAC.VerifyChain(cred_chain), "go for user 3")

# user 4 generates keys, nyms, & gets on the credential chain
even_keys4, odd_keys4 = DAC.KeyGen()
(nym_even4, sk_even4), (nym_odd4, sk_odd4) = DAC.NymGen(
*even_keys4, *odd_keys4
)
cred_chain = DAC.IssueNext(cred_chain, nym_even4, sk_odd3)
self.assertTrue(DAC.VerifyChain(cred_chain), "go for user 4")

# user 5 generates keys, nyms, & gets on the credential chain
even_keys5, odd_keys5 = DAC.KeyGen()
(nym_even5, sk_even5), (nym_odd5, sk_odd5) = DAC.NymGen(
*even_keys5, *odd_keys5
)
cred_chain = DAC.IssueNext(cred_chain, nym_odd5, sk_even4)
self.assertTrue(DAC.VerifyChain(cred_chain), "go for user 5")


if __name__ == "__main__":
unittest.main()
61 changes: 61 additions & 0 deletions python/test_mercurial_signature_scheme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import unittest

from mercurial_signature_scheme import MercurialSignatureScheme, MercurialSignatureDual


class TestMercurialSignatureScheme(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.MSS1 = MercurialSignatureScheme()
cls.MSS2 = MercurialSignatureDual()

def test_verify(self):
for MSS in [self.MSS1, self.MSS2]:
pk, sk = MSS.KeyGen(3)
M = [MSS.HashMessage(m) for m in ["this", "is a", "test"]]
sigma = MSS.Sign(sk, M)
self.assertTrue(MSS.Verify(pk, M, sigma), "test message signature verifies")

def test_convert_sig(self):
for MSS in [self.MSS1, self.MSS2]:
pk, sk = MSS.KeyGen(4)
M = [MSS.HashMessage(m) for m in ["this", "is", "another", "test"]]
sigma = MSS.Sign(sk, M)
rho = MSS.RandomZp()
pk1 = MSS.ConvertPK(pk, rho)
sigma1 = MSS.ConvertSig(pk, M, sigma, rho)
self.assertTrue(MSS.Verify(pk1, M, sigma1), "test conversion verifies")
M[0] = MSS.HashMessage("oh noes")
self.assertFalse(MSS.Verify(pk1, M, sigma1), "forgery does not verify")

def test_change_rep(self):
for MSS in [self.MSS1, self.MSS2]:
pk, sk = MSS.KeyGen(5)
M = [MSS.HashMessage(m) for m in ["this", "is", "also", "a", "test"]]
sigma = MSS.Sign(sk, M)
mu = MSS.RandomZp()
M0, sigma0 = MSS.ChangeRep(pk, M, sigma, mu)
self.assertTrue(MSS.Verify(pk, M0, sigma0), "change rep verifies")
M0[-1] = MSS.HashMessage("is bad")
self.assertFalse(MSS.Verify(pk, M0, sigma0), "forgery does not verify")

def test_underlying_groups(self):
for MSS in [self.MSS1]: # same groups for both signature schemes
self.assertEqual(
(MSS.curve.r + 1) * MSS.P, MSS.P, "ecp group order check passes"
)
self.assertEqual(
(MSS.curve.r + 1) * MSS.Phat, MSS.Phat, "ecp2 group order check passes"
)

def test_hash(self):
for MSS in [self.MSS1]: # the real hash
self.assertEqual(MSS.HashMessage("foo"), MSS.HashMessage("foo"))
self.assertEqual(MSS.HashMessage("bar"), MSS.HashMessage("bar"))
for MSS in [self.MSS1, self.MSS2]:
self.assertNotEqual(MSS.HashMessage("foo"), MSS.HashMessage("bar"))
self.assertNotEqual(MSS.HashMessage("bar"), MSS.HashMessage("baz"))


if __name__ == "__main__":
unittest.main()

0 comments on commit e4d8c0c

Please sign in to comment.