From 213519bd9337bce33d8838146bc7c35f063886f6 Mon Sep 17 00:00:00 2001 From: Peter Surda Date: Wed, 25 Dec 2019 22:09:17 +0100 Subject: [PATCH] Blind chain signature verification - also adds serialisation, deserialisation and optional metadata --- src/pyelliptic/blindchain.py | 77 +++++++++++++++++++++++++++++++++ src/pyelliptic/eccblind.py | 82 +++++++++++++++++++++++++++++++++--- src/tests/test_blindsig.py | 48 ++++++++++++++++++++- 3 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 src/pyelliptic/blindchain.py diff --git a/src/pyelliptic/blindchain.py b/src/pyelliptic/blindchain.py new file mode 100644 index 00000000..df1d3049 --- /dev/null +++ b/src/pyelliptic/blindchain.py @@ -0,0 +1,77 @@ +""" +Blind signature chain with a top level CA +""" + +from eccblind import ECCBlind + +try: + import msgpack +except ImportError: + try: + import umsgpack as msgpack + except ImportError: + import fallback.umsgpack.umsgpack as msgpack + +from eccblind import ECCBlind + +def encode_datetime(obj): + """ + Method to format time + """ + return {'Time': int(obj.strftime("%s"))} + + +class ECCBlindChain(object): # pylint: disable=too-many-instance-attributes + """ + # Class for ECC Blind Chain signature functionality + """ + chain = [] + ca = [] + + def __init__(self, chain=None): + if chain is not None: + self.chain = chain + + def serialize(self): + data = msgpack.packb(self.chain) + return data + + @staticmethod + def deserialize(self, chain): + """ + Deserialize the data using msgpack + """ + data = msgpack.unpackb(chain) + return ECCBlindChain(data) + + def add_level(self, pubkey, metadata, signature): + self.chain.append((pubkey, metadata, signature)) + + def add_ca(self, ca): + pubkey, metadata = ECCBlind.deserialize(ca) + self.ca.append(pubkey) + + def verify(self, msg, value): + lastpubkey = None + retval = False + for level in reversed(self.chain): + pubkey, metadata, signature = level + verifier_obj = ECCBlind(pubkey=pubkey, metadata) + if not lastpubkey: + retval = verifier_obj.verify(msg, signature, value) + else: + retval = verifier_obj.verify(lastpubkey, signature, value) + if not reval: + break + lastpubkey = pubkey + if retval: + retval = False + for ca in self.ca: + match = True + for i in range(4): + if lastpubkey[i] != ca[i]: + match = False + break + if match: + return True + return retval diff --git a/src/pyelliptic/eccblind.py b/src/pyelliptic/eccblind.py index 5b9b045e..6c85de25 100644 --- a/src/pyelliptic/eccblind.py +++ b/src/pyelliptic/eccblind.py @@ -10,9 +10,52 @@ http://www.isecure-journal.com/article_39171_47f9ec605dd3918c2793565ec21fcd7a.pd # variable names are based on the math in the paper, so they don't conform # to PEP8 +from hashlib import sha256 +import time + +try: + import msgpack +except ImportError: + try: + import umsgpack as msgpack + except ImportError: + import fallback.umsgpack.umsgpack as msgpack + from .openssl import OpenSSL +class Metadata(object): + """ + Pubkey metadata + """ + def __init__(self, exp=0, value=0): + self.exp = 0 + self.value = 0 + if exp: + self.exp = exp + if value: + self.value = value + + def serialize(self): + if self.exp or self.value: + return [self.exp, self.value] + else: + return [] + + @staticmethod + def deserialize(self, data): + exp, value = data + return Medatadata(exp, value) + + def verify(self, value): + if self.value and value > self.value: + return False + if self.exp: + if time.time() > self.exp: + return False + return True + + class ECCBlind(object): # pylint: disable=too-many-instance-attributes """ Class for ECC blind signature functionality @@ -75,7 +118,21 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes OpenSSL.EC_POINT_get_affine_coordinates_GFp(group, F, x0, y0, ctx) return x0 - def __init__(self, curve="secp256k1", pubkey=None): + @staticmethod + def deserialize(self, data): + pubkey_deserialized, meta = msgpack.unpackb(data) + if meta: + obj = ECCBlind(pubkey=pubkey_deserialized, metadata=meta) + else: + obj = ECCBlind(pubkey=pubkey_deserialized) + return obj + + def serialize(self): + data = (self.pubkey, self.metadata.serialize) + retval = msgpack.packb(data) + return retval + + def __init__(self, curve="secp256k1", pubkey=None, metadata=None): self.ctx = OpenSSL.BN_CTX_new() if pubkey: @@ -101,6 +158,14 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes self.iO = OpenSSL.EC_POINT_new(self.group) OpenSSL.EC_POINT_set_to_infinity(self.group, self.iO) + if metadata: + self._set_metadata(self, metadata) + else: + self.metadata = Metadata() + + def _set_metadata(self, metadata): + self.metadata = Metadata.deserialise(metadata) + def signer_init(self): """ Init signer @@ -119,6 +184,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes Requester creates a new signing request """ self.R = R + msghash = sha256(msg) # Requester: 3 random blinding factors self.F = OpenSSL.EC_POINT_new(self.group) @@ -151,7 +217,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes # Requester: Blinding (m' = br(m) + a) self.m = OpenSSL.BN_new() - OpenSSL.BN_bin2bn(msg, len(msg), self.m) + OpenSSL.BN_bin2bn(msghash, len(msghash), self.m) self.m_ = OpenSSL.BN_new() OpenSSL.BN_mod_mul(self.m_, self.b, self.r, self.n, self.ctx) @@ -180,14 +246,15 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes self.signature = (s, self.F) return self.signature - def verify(self, msg, signature): + def verify(self, msg, signature, value=0): """ Verify signature with certifier's pubkey """ # convert msg to BIGNUM self.m = OpenSSL.BN_new() - OpenSSL.BN_bin2bn(msg, len(msg), self.m) + msghash = sha256(msg) + OpenSSL.BN_bin2bn(msghash, len(msghash), self.m) # init s, self.F = signature @@ -206,5 +273,8 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes retval = OpenSSL.EC_POINT_cmp(self.group, lhs, rhs, self.ctx) if retval == -1: raise RuntimeError("EC_POINT_cmp returned an error") - else: - return retval == 0 + elif retval != 0: + return False + elif self.metadata: + return self.metadata.verify(value) + return True diff --git a/src/tests/test_blindsig.py b/src/tests/test_blindsig.py index b8d0248a..4760ad56 100644 --- a/src/tests/test_blindsig.py +++ b/src/tests/test_blindsig.py @@ -2,10 +2,12 @@ Test for ECC blind signatures """ import os +import time import unittest from ctypes import cast, c_char_p -from pybitmessage.pyelliptic.eccblind import ECCBlind +from pybitmessage.pyelliptic.eccblind import ECCBlind, Metadata +from pybitmessage.pyelliptic.eccblindchain import ECCBlindChain from pybitmessage.pyelliptic.openssl import OpenSSL @@ -50,3 +52,47 @@ class TestBlindSig(unittest.TestCase): # (5) Verification verifier_obj = ECCBlind(pubkey=signer_obj.pubkey) self.assertTrue(verifier_obj.verify(msg, signature)) + + # Serialization and deserialisation + pk = signer_obj.serialize() + pko = ECCBlind.deserialize(pk) + self.assertTrue(pko.verify(msg, signature)) + + def test_blind_sig_chain(self): + """Test blind signature chain using a random certifier key and a random message""" + + test_levels = 5 + value = 1 + msg = os.urandom(1024) + + chain = ECCBlindChain() + ca = ECCBlind() + signer_obj = ca + signer_pubkey = signer_obj.serialize() + + for level in range(test_levels): + if level == 0: + metadata = Metadata(exp=int(time.time()) + 100, + value=value).serialize() + requester_obj = ECCBlind(pubkey=signer_obj.pubkey, + metadata=metadata) + else: + requester_obj = ECCBlind(pubkey=signer_obj.pubkey) + point_r = signer_obj.signer_init() + + if level == test_levels - 1: + msg_blinded = requester_obj.create_signing_request(point_r, + msg) + else: + msg_blinded = requester.obj.create_signing_request(point_r, + signer_pubkey) + signature_blinded = signer_obj.blind_sign(msg_blinded) + signature = requester_obj.unblind(signature_blinded) + chain.add_level(signer_obj.pubkey, + signer_obj.metadata.serialize, + signature) + signer_obj = requester_obj + signer_pubkey = requester_obj.serialize() + sigchain = chain.serialize() + verifychain = ECCBlindChain.deserialize(sigchain) + self.assertTrue(verifychain.verify(msg, value))