DataTransfer/DataTransfer.Client/Services/HandshakeCodec.cs
2026-10-15 20:09:11 +02:00

53 lines
2.1 KiB
C#

namespace DataTransfer.Client.Services;
/// <summary>
/// Plaintext messages of the commit-then-reveal key exchange (see <see cref="SecureChannel" />). They are relayed
/// like frames, but before the channel is encrypted. Layout: version (byte) | type (byte) | payload.
/// </summary>
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);
/// <exception cref="InvalidDataException">The message is not a commitment.</exception>
public static byte[] DecodeCommitment(byte[] message) =>
Decode(message, MessageType.Commitment, CommitmentBytes, CommitmentBytes);
/// <exception cref="InvalidDataException">The message is not a public key.</exception>
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..];
}
}