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. """ # 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 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 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 test_tampered_credential(): ''' Test if a user can have a response to a request if the credential is wrong ''' attributes = "a,b,c,d,e" chosen_attr = "d,e" revealed_attr = "e" username = "******" message = "46.5 6.6".encode() pk, sk = Server.generate_ca(attributes) #Registration process issue_request, private_state = Client().prepare_registration(pk, username, chosen_attr) response = Server().register(sk, issue_request, username, chosen_attr) credential = Client().proceed_registration_response(pk, response, private_state) #Tampering the credential credential = jsonpickle.decode(credential) credential.credential = (credential.credential[0] ** 2, credential.credential[1]) credential = jsonpickle.encode(credential).encode() #Request a service signature = Client().sign_request(pk, credential, message, revealed_attr) assert(not Server().check_request_signature(pk, message, revealed_attr, signature))
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. """ sk = jsonpickle.decode(server_sk) attributes = attributes.split(',') #Check if attributes chosen are valid for attr in attributes: if attr not in sk[2]: raise RuntimeError("Revealed attributes are not valid") response = Issuer.issue(sk, jsonpickle.decode(issuance_request), username, attributes) return jsonpickle.encode(response).encode()
def serialize(self): """Serialize the object to a byte array. Returns: byte[]: a byte array """ data = jsonpickle.encode(self) return data.encode()
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 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)
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 proceed_registration_response(self, server_pk, server_response, private_state): """Process the response from the server. Args: server_pk (byte[]): a server's public key (serialized) server_response (byte[]): the response from the server (serialized) private_state (private_state): state from the prepare_registration request corresponding to this response Return: credential (byte []): create an attribute-based credential for the user """ credential = AnonCredential.receive_issue_response( jsonpickle.decode(server_pk)[0], jsonpickle.decode(server_response), private_state) return jsonpickle.encode(credential).encode()
def proceed_registration_response(self, server_pk, server_response, private_state): """Process the response from the server. Args: server_pk (byte[]): a server's public key (serialized) server_response (byte[]): the response from the server (serialized) private_state (private_state): state from the prepare_registration request corresponding to this response Return: credential (byte []): create an attribute-based credential for the user """ sigma = jsonpickle.decode(server_response) (random_sk, t) = private_state sigma_bis = (sigma["sigma1"], sigma["sigma2"] / (sigma["sigma1"]**t)) #Verifying the received signature pk = jsonpickle.decode(server_pk) Y_s = pk["Ys"] acc = G2.neutral_element() for (i, y_i) in zip(range(len(Y_s)), Y_s): if i == 0: acc = acc * (y_i**random_sk) else: acc = acc * y_i #Checking that the signature is valid if not (sigma_bis[0].pair(acc * pk["X"]) == sigma_bis[1].pair( G2.generator())): raise Exception("Invalid signature !") return jsonpickle.encode({ "sigma": (sigma_bis[0], sigma_bis[1]), "random_sk": random_sk }).encode()
def serialize(self): data = jsonpickle.encode(self) return data.encode()
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()