def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() #TODO ADD CODE HERE return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
class Group_ECC: "Group operations in ECC" def __init__(self, gid=713): self.G = EcGroup(gid) self.g = self.G.generator() def gensecret(self): return self.G.order().random() def expon(self, base, exp): x = exp[0] for f in exp[1:]: x = x.mod_mul(f, self.G.order()) b = base return (x * b) def expon_base(self, exp): x = exp[0] for f in exp[1:]: x = x.mod_mul(f, self.G.order()) return (x * self.g) def makeexp(self, data): return (Bn.from_binary(data) % self.G.order()) def in_group(self, alpha): # All strings of length 32 are in the group, says DJB b = alpha return self.G.check_point(b) def printable(self, alpha): return alpha.export(POINT_CONVERSION_UNCOMPRESSED)
def mix_server_one_hop(private_key, message_list): """ Implements the decoding for a simple one-hop mix. Each message is decoded in turn: - A shared key is derived from the message public key and the mix private_key. - the hmac is checked against all encrypted parts of the message - the address and message are decrypted, decoded and returned """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not len(msg.hmac) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Check the HMAC h = Hmac(b"sha512", hmac_key) h.update(msg.address) h.update(msg.message) expected_mac = h.digest() print "my hmac: " + str(msg.hmac) print "ex hmac: " + str(expected_mac[:20]) if not secure_compare(msg.hmac, expected_mac[:20]): raise Exception("HMAC check failure") ## Decrypt the address and the message iv = b"\x00"*16 address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) output = (address_full[:address_len], message_full[:message_len]) out_queue += [output] return sorted(out_queue)
def mix_server_one_hop(private_key, message_list): """ Implements the decoding for a simple one-hop mix. Each message is decoded in turn: - A shared key is derived from the message public key and the mix private_key. - the hmac is checked against all encrypted parts of the message - the address and message are decrypted, decoded and returned """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not len(msg.hmac) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Check the HMAC h = Hmac(b"sha512", hmac_key) h.update(msg.address) h.update(msg.message) expected_mac = h.digest()[:20] if not secure_compare(msg.hmac, expected_mac[:20]): raise Exception("HMAC check failure") ## Decrypt the address and the message iv = b"\x00" * 16 # Why are we using an all zero IV?! # iv = urandom(16) address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) output = (address_full[:address_len], message_full[:message_len]) out_queue += [output] return sorted(out_queue)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE # First get a shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Encrypt the address and the message iv = b"\x00"*16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) ## Form the HMAC h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] assert len(address_cipher) == 258 assert len(message_cipher) == 1002 assert len(expected_mac) == 20 return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() # generate shared element shared = public_key.pt_mul(private_key) material = sha512(shared.export()).digest() # split shared key for different operations hmac_key = material[:16] address_key = material[16:32] message_key = material[32:48] # random inistialisation vector iv = b"\x00" * 16 # encoding address and message address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) # generate hmac h h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest() expected_mac = expected_mac[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() shared_element = public_key.pt_mul(private_key) key_material = sha512(shared_element.export()).digest() hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## ADD CODE HERE # TODO: # - Encode message to be processed by mix_server_one_hop # client_public_key is an EC point # expected_hmac is the hmac of address_cipher and message_cipher # address_cipher and message_cipher are AES-CTR ciphertexts # - Make sure mix_server_one_hop decodes message correctly iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() # generate shared element shared = public_key.pt_mul(private_key) material = sha512(shared.export()).digest() # split shared key for different operations hmac_key = material[:16] address_key = material[16:32] message_key = material[32:48] # random inistialisation vector iv = b"\x00"*16 # encoding address and message address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) # generate hmac h h = Hmac(b"sha512",hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest() expected_mac = expected_mac[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() encryption_key = private_key * public_key ek = sha512(encryption_key.export()).digest() #TODO ADD CODE HERE #get coresponding key parts hmac_key = ek[:16] address_key = ek[16:32] message_key = ek[32:48] #encrypt iv = b"\x00"*16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) #calculate mac h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest() expected_mac = expected_mac[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_server_n_hop(private_key, message_list, final=False): """ Decodes a NHopMixMessage message and outputs either messages destined to the next mix or a list of tuples (address, message) (if final=True) to be sent to their final recipients. Broadly speaking the mix will process each message in turn: - it derives a shared key (using its private_key), - checks the first hmac, - decrypts all other parts, - either forwards or decodes the message. """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not isinstance(msg.hmacs, list) or \ not len(msg.hmacs[0]) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Extract a blinding factor for the public_key blinding_factor = Bn.from_binary(key_material[48:]) new_ec_public_key = blinding_factor * msg.ec_public_key ## Check the HMAC h = Hmac(b"sha512", hmac_key) for other_mac in msg.hmacs[1:]: h.update(other_mac) h.update(msg.address) h.update(msg.message) expected_mac = h.digest() if not secure_compare(msg.hmacs[0], expected_mac[:20]): raise Exception("HMAC check failure") ## Decrypt the hmacs, address and the message aes = Cipher("AES-128-CTR") # Decrypt hmacs new_hmacs = [] for i, other_mac in enumerate(msg.hmacs[1:]): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs += [hmac_plaintext] # Decrypt address & message iv = b"\x00" * 16 address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) if final: # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) out_msg = (address_full[:address_len], message_full[:message_len]) out_queue += [out_msg] else: # Pass the new mix message to the next mix out_msg = NHopMixMessage(new_ec_public_key, new_hmacs, address_plaintext, message_plaintext) out_queue += [out_msg] return out_queue
def test_protocol(): # Parameters of the BL schemes G = EcGroup(713) q = G.order() g = G.hash_to_point(b"g") h = G.hash_to_point(b"h") z = G.hash_to_point(b"z") hs = [G.hash_to_point(("h%s" % i).encode("utf8")) for i in range(100)] # Inputs from user R = q.random() L1 = 10 L2 = 20 C = R * hs[0] + L1 * hs[1] + L2 * hs[2] m = b"Hello World!" # Inputs from the Issuer # TODO: check ZK on C x = q.random() y = x * g # Preparation rnd = q.random() z1 = C + rnd * g z2 = z + (-z1) ## Send: (rnd,) to user if rnd % q == 0: raise z1 = C + rnd * g gam = q.random() zet = gam * z zet1 = gam * z1 zet2 = zet + (-zet1) tau = q.random() eta = tau * z # Validation: Issuer u, r1p, r2p, cp = [q.random() for _ in range(4)] a = u * g a1p = r1p * g + cp * z1 a2p = r2p * h + cp * z2 ## Send(a, ap = (a1p, a2p)) # User side assert G.check_point(a) assert G.check_point(a1p) assert G.check_point(a2p) t1, t2, t3, t4, t5 = [q.random() for _ in range(5)] alph = a + t1 * g + t2 * y alph1 = gam * a1p + t3 * g + t4 * zet1 alph2 = gam * a2p + t5 * h + t4 * zet2 # Make epsilon H = [zet, zet1, alph, alph1, alph2, eta] Hstr = list(map(EcPt.export, H)) + [m] Hhex = b"|".join(map(b64encode, Hstr)) epsilon = Bn.from_binary(sha256(Hhex).digest()) % q e = epsilon.mod_sub(t2, q).mod_sub(t4, q) ## Send: (e,) to Issuer c = e.mod_sub(cp, q) r = u.mod_sub((c * x), q) ## Send: (c,r, cp, rp = (r1p, r2p)) to User ro = r.mod_add(t1, q) om = c.mod_add(t2, q) ro1p = (gam * r1p + t3) % q ro2p = (gam * r2p + t5) % q omp = (cp + t4) % q mu = (tau - omp * gam) % q signature = (m, zet, zet1, zet2, om, omp, ro, ro1p, ro2p) # Check verification equation lhs = (om + omp) % q rhs_h = [ zet, zet1, ro * g + om * y, ro1p * g + omp * zet1, ro2p * h + omp * zet2, ## problem mu * z + omp * zet ] Hstr = list(map(EcPt.export, rhs_h)) + [m] Hhex = b"|".join(map(b64encode, Hstr)) rhs = Bn.from_binary(sha256(Hhex).digest()) % q # Check the (future) ZK proof assert zet == gam * z gam_hs = [gam * hsi for hsi in hs] gam_g = gam * g assert rnd * gam_g + R * gam_hs[0] + L1 * gam_hs[1] + L2 * gam_hs[2] == zet1 print(rhs == lhs)
def mix_server_n_hop(private_key, message_list, use_blinding_factor=False, final=False): """ Decodes a NHopMixMessage message and outputs either messages destined to the next mix or a list of tuples (address, message) (if final=True) to be sent to their final recipients. Broadly speaking the mix will process each message in turn: - it derives a shared key (using its private_key), - checks the first hmac, - decrypts all other parts, - Either forwards or decodes the message. The implementation of the blinding factor is optional and therefore only activated in the bonus tests. """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not isinstance(msg.hmacs, list) or \ not len(msg.hmacs[0]) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Extract a blinding factor for the public_key # (only if you're brave enough for the bonus task) new_ec_public_key = msg.ec_public_key if (use_blinding_factor): blinding_factor = Bn.from_binary(key_material[48:]) new_ec_public_key = blinding_factor * msg.ec_public_key ## Check the HMAC h = Hmac(b"sha512", hmac_key) for other_mac in msg.hmacs[1:]: h.update(other_mac) h.update(msg.address) h.update(msg.message) expected_mac = h.digest() if not secure_compare(msg.hmacs[0], expected_mac[:20]): raise Exception("HMAC check failure") # Decrypt hmacs new_hmacs = [] for i, other_mac in enumerate(msg.hmacs[1:]): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00"*14) hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs += [hmac_plaintext] # Decrypt address & message iv = b"\x00"*16 address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) if final: # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) out_msg = (address_full[:address_len], message_full[:message_len]) out_queue += [out_msg] else: # Pass the new mix message to the next mix out_msg = NHopMixMessage(new_ec_public_key, new_hmacs, address_plaintext, message_plaintext) out_queue += [out_msg] return out_queue
def test_protocol(): # Parameters of the BL schemes G = EcGroup(713) q = G.order() g = G.hash_to_point(b"g") h = G.hash_to_point(b"h") z = G.hash_to_point(b"z") hs = [G.hash_to_point(("h%s" % i).encode("utf8")) for i in range(100)] # Inputs from user R = q.random() L1 = 10 L2 = 20 #age C = R * hs[0] + L1 * hs[1] + L2 * hs[2] m = b"Hello World!" # Inputs from the Issuer # TODO: check ZK on C x = q.random() y = x * g # Preparation rnd = q.random() z1 = C + rnd * g z2 = z + (-z1) ## Send: (rnd,) to user if rnd % q == 0: raise z1 = C + rnd * g gam = q.random() zet = gam * z zet1 = gam * z1 zet2 = zet + (-zet1) tau = q.random() eta = tau * z # Validation: Issuer u, r1p, r2p, cp = [q.random() for _ in range(4)] a = u * g a1p = r1p * g + cp * z1 a2p = r2p * h + cp * z2 ## Send(a, ap = (a1p, a2p)) # User side assert G.check_point(a) assert G.check_point(a1p) assert G.check_point(a2p) t1, t2, t3, t4, t5 = [q.random() for _ in range(5)] alph = a + t1 * g + t2 * y alph1 = gam * a1p + t3 * g + t4 * zet1 alph2 = gam * a2p + t5 * h + t4 * zet2 # Make epsilon H = [zet, zet1, alph, alph1, alph2, eta] Hstr = list(map(EcPt.export, H)) + [m] Hhex = b"|".join(map(b64encode, Hstr)) epsilon = Bn.from_binary(sha256(Hhex).digest()) % q e = epsilon.mod_sub(t2, q).mod_sub(t4, q) ## Send: (e,) to Issuer c = e.mod_sub(cp, q) r = u.mod_sub((c * x), q) ## Send: (c,r, cp, rp = (r1p, r2p)) to User ro = r.mod_add(t1, q) om = c.mod_add(t2, q) ro1p = (gam * r1p + t3) % q ro2p = (gam * r2p + t5) % q omp = (cp + t4) % q mu = (tau - omp * gam) % q signature = (m, zet, zet1, zet2, om, omp, ro, ro1p, ro2p) gam_hs = [gam * hsi for hsi in hs] zet1p = zet1 - L2 * gam_hs[2] # Check verification equation lhs = (om + omp) % q rhs_h = [zet, zet1p, ro * g + om * y, ro1p * g + omp * zet1p, ro2p * h + omp * zet2, ## problem mu * z + omp * zet] Hstr = list(map(EcPt.export, rhs_h)) + [m] Hhex = b"|".join(map(b64encode, Hstr)) rhs = Bn.from_binary(sha256(Hhex).digest()) % q # Check the (future) ZK proof assert zet == gam * z gam_hs = [gam * hsi for hsi in hs] gam_g = gam * g #assert rnd * gam_g + R * gam_hs[0] + L1 * gam_hs[1] + L2 * gam_hs[2] == zet1 assert rnd * gam_g + R * gam_hs[0] + L1 * gam_hs[1] == zet1 - L2 * gam_hs[2] print(rhs == lhs)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # This assertion was originally commented out, can instead check each of the keys one by one for public_key in public_keys: assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE # Calculate shared keys, taking into account blinding shared_elems = [] blinded = [] new = public_keys[0] for i, public_key in enumerate(public_keys): shared = private_key * new shared_elems.append(shared) if i == len(public_keys) - 1: break key_material = sha512(shared.export()).digest() blinding = Bn.from_binary(key_material[48:]) blinded.append(blinding) blind = blinded[0] for b in range(len(blinded[1:])): blind = blind * blinded[b + 1] new = blind * public_keys[i + 1] # Shared keys are used 'backwards' shared_elems.reverse() public_keys.reverse() # At the start of the encryption chain, the payload is in plaintext address_cipher = address_plaintext message_cipher = message_plaintext # Perform the encoding at each stage, using the shared elements calculated hmacs = [] for i, public_key in enumerate(public_keys): shared_element = shared_elems[i] # Get the appropriate key from each part of the shared key key_material = sha512(shared_element.export()).digest() hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Encrypt the message and address at each stage iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Encrypt the hmacs at each stage for j, hm in enumerate(hmacs): iv = pack("H14s", j, b"\x00" * 14) hmac_plaintext = aes_ctr_enc_dec(key_material[:16], iv, hm) hmacs[j] = hmac_plaintext # Generate the expected mac for each stage h = Hmac(b"sha512", hmac_key) for hm in hmacs: h.update(hm) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] # Each hmac is inserted at the start of the list, as the last hmac is the first to be # inspected - the last encryption is also the first to be decrypted hmacs.insert(0, expected_mac) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)