namespace DataTransfer.Client.Services;
///
/// Plaintext messages of the commit-then-reveal key exchange (see ). They are relayed
/// like frames, but before the channel is encrypted. Layout: version (byte) | type (byte) | payload.
///
internal static class HandshakeCodec
{
public const int CommitmentBytes = 32;
// A P-256 SPKI key has 91 bytes; anything much larger is not a key.
private const int MaxPublicKeyBytes = 512;
private const byte Version = 1;
private enum MessageType : byte
{
Commitment = 1,
PublicKey = 2
}
public static byte[] EncodeCommitment(byte[] commitment) => Encode(MessageType.Commitment, commitment);
public static byte[] EncodePublicKey(byte[] publicKey) => Encode(MessageType.PublicKey, publicKey);
/// The message is not a commitment.
public static byte[] DecodeCommitment(byte[] message) =>
Decode(message, MessageType.Commitment, CommitmentBytes, CommitmentBytes);
/// The message is not a public key.
public static byte[] DecodePublicKey(byte[] message) => Decode(message, MessageType.PublicKey, 1, MaxPublicKeyBytes);
private static byte[] Encode(MessageType type, byte[] payload)
{
var message = new byte[payload.Length + 2];
message[0] = Version;
message[1] = (byte)type;
payload.CopyTo(message, 2);
return message;
}
private static byte[] Decode(byte[] message, MessageType type, int minPayloadBytes, int maxPayloadBytes)
{
ArgumentNullException.ThrowIfNull(message);
var payloadBytes = message.Length - 2;
if (payloadBytes < minPayloadBytes || payloadBytes > maxPayloadBytes || message[0] != Version || message[1] != (byte)type)
{
throw new InvalidDataException($"Expected a valid key-exchange message of type {type}.");
}
return message[2..];
}
}