From e4d8c0cb327a84b5b12d090238a9858ffe2eb787 Mon Sep 17 00:00:00 2001 From: mburkhar Date: Fri, 7 Aug 2020 15:50:53 -0700 Subject: [PATCH] Python code for DAC using mercurial sigs --- python/delegatable_anon_cred_scheme.py | 65 +++++++++++ python/mercurial_signature_scheme.py | 120 ++++++++++++++++++++ python/pyproject.toml | 15 +++ python/test_delegatable_anon_cred_scheme.py | 58 ++++++++++ python/test_mercurial_signature_scheme.py | 61 ++++++++++ 5 files changed, 319 insertions(+) create mode 100644 python/delegatable_anon_cred_scheme.py create mode 100644 python/mercurial_signature_scheme.py create mode 100644 python/pyproject.toml create mode 100644 python/test_delegatable_anon_cred_scheme.py create mode 100644 python/test_mercurial_signature_scheme.py diff --git a/python/delegatable_anon_cred_scheme.py b/python/delegatable_anon_cred_scheme.py new file mode 100644 index 0000000..8911e69 --- /dev/null +++ b/python/delegatable_anon_cred_scheme.py @@ -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 diff --git a/python/mercurial_signature_scheme.py b/python/mercurial_signature_scheme.py new file mode 100644 index 0000000..1b7d112 --- /dev/null +++ b/python/mercurial_signature_scheme.py @@ -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() diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..5b1afa7 --- /dev/null +++ b/python/pyproject.toml @@ -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 +)/ +''' diff --git a/python/test_delegatable_anon_cred_scheme.py b/python/test_delegatable_anon_cred_scheme.py new file mode 100644 index 0000000..a967a10 --- /dev/null +++ b/python/test_delegatable_anon_cred_scheme.py @@ -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() diff --git a/python/test_mercurial_signature_scheme.py b/python/test_mercurial_signature_scheme.py new file mode 100644 index 0000000..6abeaf9 --- /dev/null +++ b/python/test_mercurial_signature_scheme.py @@ -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()