def generate_ca(valid_attributes): """Initializes the credential system. Runs exactly once in the beginning. Decides on schemes public parameters and choses a secret key for the server. Args: valid_attributes (string): a list of all valid attributes. Users cannot get a credential with a attribute which is not included here. Note: You can use JSON to encode valid_attributes in the string. Returns: (tuple): tuple containing: byte[] : server's pubic information byte[] : server's secret key You are free to design this as you see fit, but all commuincations needs to be encoded as byte arrays. """ # Code to generate the secret key private_key_array = [] public_key_array = [] ys_in_G1 = [] g = G2.generator() for i in range(L + 1): c = G1.order().random() private_key_array.append(c) public_key_array.append(g**c) ys_in_G1.append(G1.generator()**c) x = private_key_array[0] X = public_key_array[0] X_G1 = G1.generator()**x y_s = private_key_array[1:] Y_s = public_key_array[1:] ys_in_G1 = ys_in_G1[1:] # Some sanity checks on the key for i in range(len(y_s)): if not (G1.generator().pair(Y_s[i]) == ys_in_G1[i].pair( G2.generator())): raise Exception("Key generation failed !") public_information = { "Ys": Y_s, "X_G1": X_G1, "X": X, "Ys_G1": ys_in_G1, "validAttributes": valid_attributes.split(",") } pk = jsonpickle.encode(public_information).encode() private_key = { "Ys": Y_s, "X_G1": X_G1, "X": X, "Ys_G1": ys_in_G1, "validAttributes": valid_attributes.split(","), "x": x, "ys": y_s } return pk, jsonpickle.encode(private_key).encode()
def sign(cls, sk, messages): m = Bn.from_binary(hashlib.sha256(messages).digest()) h = G1.generator() ** G1.order().random() while h == G1.neutral_element(): h = G1.generator() ** G1.order().random() sig = [h, h ** (sk[0] + sk[1] * m)] return sig
def create_issue_request(server_pk, attributes): """Gets all known attributes (subscription) of a user and creates an issuance request. You are allowed to add extra attributes to the issuance. You should design the issue_request as you see fit. """ attributes = [Bn.from_binary(hashlib.sha256(attr.encode()).digest()) for attr in attributes] gen_g1 = server_pk[0] t = G1.order().random() #Gen C C = gen_g1 ** t for e in zip(server_pk[1:], attributes): C = C * e[0] ** e[1] #Gen commitment comm_values = [G1.order().random() for _ in range(len(attributes) + 1)] comm = gen_g1 ** comm_values[0] for e in zip(server_pk[1:], comm_values[1:]): comm = comm * e[0] ** e[1] #Gen challenge challenge = hashlib.sha256(jsonpickle.encode(C).encode()) challenge.update(jsonpickle.encode(comm).encode()) challenge.update(jsonpickle.encode(server_pk).encode()) challenge = Bn.from_binary(challenge.digest()) #Generate response response = [e[0].mod_sub(challenge * e[1],G1.order()) for e in zip(comm_values, [t] + attributes)] return IssuanceRequest(C, comm, challenge, response),t
def generate_ca(valid_attributes): """Initializes the credential system. Runs exactly once in the beginning. Decides on schemes public parameters and choses a secret key for the server. Args: valid_attributes (string): a list of all valid attributes. Users cannot get a credential with a attribute which is not included here. Note: You can use JSON to encode valid_attributes in the string. Returns: (tuple): tuple containing: byte[] : server's pubic information byte[] : server's secret key You are free to design this as you see fit, but all commuincations needs to be encoded as byte arrays. """ attr_list = valid_attributes.split(',') nb_attributes = len(attr_list) gen_g1 = G1.generator() gen_g2 = G2.generator() exp = [G1.order().random() for _ in range(nb_attributes + 1)] pk = [gen_g1] + [gen_g1**i for i in exp[1:] ] + [gen_g2] + [gen_g2**i for i in exp] sk = gen_g1**exp[0] sk = [sk, pk, attr_list] pk = [pk, attr_list] return (jsonpickle.encode(pk).encode(), jsonpickle.encode(sk).encode())
def sign(self, message, revealed_attr): """Signs the message. Args: message (byte []): message revealed_attr (string []): a list of revealed attributes Return: Signature: signature """ #public_key separation nb_attr_public_key = (len(self.server_pk) - 3) // 2 gen_g1_pk = self.server_pk[0] public_key1 = self.server_pk[1:nb_attr_public_key + 1] gen_g2_pk = self.server_pk[nb_attr_public_key + 1] x_g2_pk = self.server_pk[nb_attr_public_key + 2] public_key2 = self.server_pk[nb_attr_public_key + 3:] #Gen signature r = G1.order().random() t = G1.order().random() signature = (self.credential[0] ** r, (self.credential[1] * self.credential[0]**t)**r) #attributes work revealed_attributes_idx = [self.attributes.index(attr) for attr in self.attributes if attr in revealed_attr] revealed_attributes_bn = [Bn.from_binary(hashlib.sha256(attr.encode()).digest()) for attr in revealed_attr] hidden_attributes_idx = [self.attributes.index(attr) for attr in self.attributes if attr not in revealed_attr] hidden_attributes_bn = [Bn.from_binary(hashlib.sha256(attr.encode()).digest()) for attr in self.attributes if attr not in revealed_attr] #Gen C (left-hand side) C = signature[1].pair(gen_g2_pk) / signature[0].pair(x_g2_pk) for i in range(len(revealed_attr)): C = C * signature[0].pair(public_key2[revealed_attributes_idx[i]]) ** (-revealed_attributes_bn[i] % G1.order()) #Gen commitment (to prove right-hand side) comm_values = [G1.order().random() for _ in range(len(hidden_attributes_idx) + 1)] comm = signature[0].pair(gen_g2_pk) ** comm_values[0] for e in zip(hidden_attributes_idx, comm_values[1:]): comm = comm * signature[0].pair(public_key2[e[0]])**e[1] #Gen Challenge challenge = hashlib.sha256(jsonpickle.encode(C).encode()) challenge.update(jsonpickle.encode(comm).encode()) challenge.update(jsonpickle.encode(self.server_pk).encode()) challenge.update(message) challenge = Bn.from_binary(challenge.digest()) #Gen Responses response = [e[0].mod_sub(challenge * e[1],G1.order()) for e in zip(comm_values, [t] + hidden_attributes_bn)] return Signature(signature, comm, challenge, response, revealed_attributes_idx)
def register(self, server_sk, issuance_request, username, attributes): """ Registers a new account on the server. Args: server_sk (byte []): the server's secret key (serialized) issuance_request (bytes[]): The issuance request (serialized) username (string): username attributes (string): attributes Note: You can use JSON to encode attributes in the string. Return: response (bytes[]): the client should be able to build a credential with this response. """ #Reconstructing the secret key sk = jsonpickle.decode(server_sk) #Decoding the request decoded_request = jsonpickle.decode(issuance_request) C = decoded_request["C"] #Verifying the value for C s_sk = decoded_request["s_sk"] s_t = decoded_request["s_t"] R = decoded_request["R"] c = int.from_bytes( sha256((username + attributes).encode()).digest(), "big") % G1.order() # hashing if (sk["Ys_G1"][0]**s_sk) * (G1.generator()**s_t) * C**c != R: raise Exception("Invalid register !") #Generating a random u u = G1.order().random() g_pow_u = G1.generator()**u X = sk[ "X_G1"] #Note : I decided to have X in the secret key, rather than recompute it everytime #Building the Ys Ys = sk["Ys_G1"] sigma_2 = X * C #The subscription attributes for att in attributes.split(","): if att not in sk["validAttributes"]: raise ValueError("Non-recognized attribute") else: index_of_att = sk["validAttributes"].index(att) y_i = Ys[index_of_att + 1] #+1 because of the hidden attribute sigma_2 = sigma_2 * y_i json_res = {"sigma1": g_pow_u, "sigma2": sigma_2**u} return jsonpickle.encode(json_res).encode()
def check_request_signature(self, server_pk, message, revealed_attributes, signature): """ Args: server_pk (byte[]): the server's public key (serialized) message (byte[]): The message to sign revealed_attributes (string): revealed attributes signature (bytes[]): user's autorization (serialized) Note: You can use JSON to encode revealed_attributes in the string. Returns: valid (boolean): is signature valid """ sig = jsonpickle.decode(signature) att = revealed_attributes.split(",") pk = jsonpickle.decode(server_pk) valid_attributes = pk["validAttributes"] R = sig["R"] c = sig["c"] s_is = sig["s_is"] sigma1 = sig["sigma"][0] sigma2 = sig["sigma"][1] if sigma1 == G1.generator()**G1.order(): #Checking that sigma1 != 1 return False # Verifying the hash was legit c_us = int.from_bytes(sha256(message).digest(), "big") % G1.order() if (c_us != c): return False # Need to recreate the value the proof of knowledge is showing acc = sigma2.pair(G2.generator()) / sigma1.pair(pk["X"]) for i in range(len(att)): acc = acc / (sigma1).pair( pk["Ys"][valid_attributes.index(att[i]) + 1]) values_to_prove = [ ] # Storing the values used in the proof of knowledge values_to_prove.append((sigma1).pair(G2.generator())) values_to_prove.append(sigma1.pair(pk["Ys"][0])) for att in valid_attributes: if att not in revealed_attributes.split(","): values_to_prove.append( sigma1.pair(pk["Ys"][valid_attributes.index(att) + 1])) thing_to_add_to_r = acc**c_us for v, exp in zip(values_to_prove, s_is): thing_to_add_to_r = thing_to_add_to_r * (v**exp) return R == thing_to_add_to_r
def test_random_sigma(): """ This test aims at verifying that if the sigmas aren't conform they will be refused """ server = Server() client = Client() #Generating the keys pk_serialized, sk_serialized = server.generate_ca("a,b,c") #Registering the user on the server m = b"some message for test" c = int.from_bytes(sha256(m).digest(), "big") % G1.order() credential = jsonpickle.encode({"R":3, "c":c, "sigma": (G1.generator() ** G1.order().random(), G1.generator() ** G1.order().random()), "random_sk": 1}) #Trying to sign a message sig = client.sign_request(pk_serialized, credential, m,"a,b") #Verifying the signature assert server.check_request_signature(pk_serialized, m, "a,b", sig) == False
def test_pair_type(): g1 = G1.generator() g2 = G2.generator() a = g1**3 b = g2**5 c = a.pair(b) assert isinstance(c, GTElement) d = g1**7 with pytest.raises(Exception): a.pair(c) with pytest.raises(Exception): a.pair(d) with pytest.raises(Exception): a.pair(11)
def issue(sk, request, username, attributes): """Issues a credential for a new user. This function should receive a issuance request from the user (AnonCredential.create_issue_request), and a list of known attributes of the user (e.g. the server received bank notes for subscriptions x, y, and z). You should design the issue_request as you see fit. """ #extract public and secret key secret_key = sk[0] public_key = sk[1] #Derive challenge challenge = hashlib.sha256(jsonpickle.encode(request.C).encode()) challenge.update(jsonpickle.encode(request.commitment).encode()) challenge.update(jsonpickle.encode(public_key).encode()) challenge = Bn.from_binary(challenge.digest()) #Compare the derived challenge to the received challenge challenge_valid = challenge == request.challenge #Compute the zkp candidate = request.C ** challenge for e in zip(public_key, request.response): candidate = candidate * e[0] ** e[1] proof_valid = request.commitment == candidate #If the proof and the derived challenge is valid, sig the credential if proof_valid and challenge_valid: u = G1.order().random() sig = (public_key[0] ** u,(secret_key * request.C) ** u) return sig else : raise ValueError
def verify(cls, pk, messages, signature): m = Bn.from_binary(hashlib.sha256(messages).digest()) is_gen = signature[0] == G1.neutral_element() is_valid = signature[0].pair(pk[1] * pk[2] ** m) == signature[1].pair(pk[0]) return is_valid and not is_gen
def generate_key(cls): gen = G2.generator() sk = [G1.order().random(),G1.order().random()] pk = [gen] + [gen ** i for i in sk] return sk, pk
def verify(self, issuer_public_info, public_attrs, message): """Verifies a signature. Args: issuer_public_info (): output of issuer's 'get_serialized_public_key' method public_attrs (dict): public attributes message (byte []): list of messages returns: valid (boolean): is signature valid """ #public_key separation nb_attr_public_key = (len(issuer_public_info) - 3) // 2 gen_g1_pk = issuer_public_info[0] public_key1 = issuer_public_info[1:nb_attr_public_key + 1] gen_g2_pk = issuer_public_info[nb_attr_public_key + 1] x_g2_pk = issuer_public_info[nb_attr_public_key + 2] public_key2 = issuer_public_info[nb_attr_public_key + 3:] #attributes work nb_attr = len(self.response) - 1 + len(public_attrs) public_attributes_idx = self.attributes_idx public_attributes_bn = [Bn.from_binary(hashlib.sha256(attr.encode()).digest()) for attr in public_attrs] hidden_attributes_idx = [i for i in range(nb_attr) if i not in public_attributes_idx] #Gen C (left-hand side) C = self.signature[1].pair(gen_g2_pk) / self.signature[0].pair(x_g2_pk) for i in range(len(public_attrs)): C = C * self.signature[0].pair(public_key2[public_attributes_idx[i]]) ** (-public_attributes_bn[i] % G1.order()) #Gen Challenge challenge = hashlib.sha256(jsonpickle.encode(C).encode()) challenge.update(jsonpickle.encode(self.commitment).encode()) challenge.update(jsonpickle.encode(issuer_public_info).encode()) challenge.update(message) challenge = Bn.from_binary(challenge.digest()) #check challenge challenge_valid = challenge == self.challenge #Compute zkp candidate = C ** challenge * self.signature[0].pair(gen_g2_pk) ** self.response[0] for e in zip(hidden_attributes_idx, self.response[1:]): candidate = candidate * self.signature[0].pair(public_key2[e[0]]) ** e[1] proof_valid = candidate == self.commitment return challenge_valid and proof_valid
def sign_request(self, server_pk, credential, message, revealed_info): """Signs the request with the clients credential. Arg: server_pk (byte[]): a server's public key (serialized) credential (byte[]): client's credential (serialized) message (byte[]): message to sign revealed_info (string): attributes which need to be authorized Note: You can use JSON to encode revealed_info. Returns: byte []: message's signature (serialized) """ # Generating r,t r = G1.order().random() t = G1.order().random() #Decoding the public key pk = jsonpickle.decode(server_pk) valid_attributes = pk["validAttributes"] decoded_credential = jsonpickle.decode(credential) sigma = decoded_credential["sigma"] random_sk = decoded_credential["random_sk"] sigma_bis = (sigma[0]**r, ((sigma[0]**t) * sigma[1])**r) values_to_prove = [ ] # Storing the values used in the proof of knowledge com = (sigma_bis[0]).pair(G2.generator())**t values_to_prove.append((sigma_bis[0]).pair(G2.generator())) # The secret part of the multiplication com = com * sigma_bis[0].pair(pk["Ys"][0]**random_sk) values_to_prove.append(sigma_bis[0].pair(pk["Ys"][0])) # Attributes for att in valid_attributes: if att not in revealed_info.split(","): #raise ValueError("Attribute not in the possible ones !") com = com * sigma_bis[0].pair( pk["Ys"][valid_attributes.index(att) + 1]) values_to_prove.append(sigma_bis[0].pair( pk["Ys"][valid_attributes.index(att) + 1])) c = int.from_bytes(sha256(message).digest(), "big") % G1.order() # hashing #Adding the signature r_is = [] R = GT.generator()**GT.order() # The neutral element of the group for value in values_to_prove: r_i = G1.order().random() r_is.append(r_i) R = R * (value**r_i) # creating the R part of the protocol s_is = [] for (i, r_i) in zip(range(len(r_is)), r_is): tmp = r_i - c % G1.order() if i == 0: tmp = (r_i - c * t) % G1.order() if i == 1: tmp = (r_i - c * random_sk) % G1.order() if tmp < 0: tmp += G1.order() s_is.append(tmp) proof = {"c": c, "R": R, "s_is": s_is, "sigma": sigma_bis} return jsonpickle.encode(proof).encode()
def prepare_registration(self, server_pk, username, attributes): """Prepare a request to register a new account on the server. Args: server_pk (byte[]): a server's public key (serialized) username (string): username attributes (string): user's attributes Note: You can use JSON to encode attributes in the string. Return: tuple: byte[]: an issuance request (private_state): You can use state to store and transfer information from prepare_registration to proceed_registration_response. You need to design the state yourself. """ t = G1.order().random() pk = jsonpickle.decode(server_pk) Ys_in_G1 = pk["Ys_G1"] random_sk = G1.order().random() # Some secret key as asked C = (G1.generator()**t) * (Ys_in_G1[0]**random_sk) for att in attributes.split(","): if att not in pk["validAttributes"]: raise ValueError( "Attribute not in list of accepted attribute by the server" ) issuance_request = {} issuance_request["C"] = C r_sk = G1.order().random() r_t = G1.order().random() c = int.from_bytes( sha256((username + attributes).encode()).digest(), "big") % G1.order() # hashing s_sk = ((r_sk - random_sk * c) + G1.order()) % G1.order() s_t = ((r_t - t * c) + G1.order()) % G1.order() R = (Ys_in_G1[0]**r_sk) * (G1.generator()**r_t) if (Ys_in_G1[0]**s_sk) * (G1.generator()**s_t) * C**c != R: raise Exception("Invalid signature !") issuance_request["s_t"] = s_t issuance_request["s_sk"] = s_sk issuance_request["R"] = R return jsonpickle.encode(issuance_request).encode(), ( random_sk, t ) #t is the inner state, we only need to remember that (since the key as secret attribute is completely useless)