padded_payload = security_param + payload + pad_separator + padding
len(security_param) = 16 # do we really need this?
len(pad_separator) = 1
max_padded_payload_size = 2048
max_payload_size
= max_padded_payload_size - len(security_param) - len(pad_separator)
= 2031
max_layers = 3
##########
def new_header(
initial_ephermeral_sk: X25519Privkey,
layer_pks: list[X25519Pubkey],
):
keysets = derive_keysets(initial_emphermeral_sk, layer_pks)
fillers = TODO()
routing_capsule = build_routing_capsule(keysets)
def derive_keysets(
initial_ephermeral_sk: X25519Privkey,
layer_pks: list[X25519Pubkey],
) -> list[KeySet]:
dh_shared_keys = layer_pks.map(|pk| initial_ephermeral_sk.exchange(pk))
keysets = dh_shared_keys.map(|k| derive_keyset(k))
return initial_ephermeral_sk.public(), keysets
def derive_keyset(dh_shared_key) -> KeySet:
key = HKDF-SHA256(dh_shared_key)
assert len(key) == 256
header_enckey = key[0:16] # 16 bytes (128bits) for AES128
header_hmac_key = key[16:32] # 16 bytes for HMAC authn
payload_key = key[32:244] # 192 bytes for ChaCha20
blinding_factor = key[224:] # 32 bytes (NOT USED IN OURS) for deriving a shared key for a next mix node
return KeySet(header_enckey, header_hmac_key, payload_key, blinding_factor)
def build_routing_capsule(keysets: list[KeySet]) -> RoutingCapsule:
capsule = None
for keyset in keysets:
capsule = encapsule_routing(keyset, capsule)
return capsule
class RoutingCapsule:
encrypted_routing: bytes
integrity_hmac: bytes
class Routing:
# TODO: add more if necessary
next_truncated_encrypted_routing: bytes
def encapsule_routing(keyset: KeySet, capsule: RoutingCapsule | None) -> RoutingCapsule:
routing = Routing()