< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Transport.Handshake.States.HandshakeState
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Transport/Handshake/States/HandshakeState.cs
Tag: 36_15743069263
Line coverage
82%
Covered lines: 119
Uncovered lines: 25
Coverable lines: 144
Total lines: 347
Line coverage: 82.6%
Branch coverage
72%
Covered branches: 55
Total branches: 76
Branch coverage: 72.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)60%11.031078.26%
get_RemoteStaticPublicKey()100%210%
WriteMessage(...)73.68%23.161977.42%
ReadMessage(...)76.19%26.62176.67%
ProcessPreMessages()66.67%15.361271.43%
EnqueueMessages()100%22100%
WriteE(...)100%11100%
WriteS(...)100%11100%
ReadE(...)100%22100%
ReadS(...)50%4.02490%
ProcessEs()100%22100%
ProcessSe()100%22100%
Split()100%11100%
DhAndMixKey(...)100%11100%
Clear()100%11100%
Dispose()50%2.02283.33%
Finalize()100%210%

File(s)

/home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Transport/Handshake/States/HandshakeState.cs

#LineLine coverage
 1using System.Diagnostics;
 2
 3namespace NLightning.Infrastructure.Transport.Handshake.States;
 4
 5using Crypto.Interfaces;
 6using Domain.Crypto.Constants;
 7using Domain.Crypto.ValueObjects;
 8using Domain.Utils;
 9using Enums;
 10using Interfaces;
 11using MessagePatterns;
 12using Protocol.Constants;
 13
 14/// <inheritdoc/>
 15/// <remarks> See <see href="https://github.com/lightning/bolts/blob/master/08-transport.md">Lightning Bolt8</see> for s
 16internal sealed class HandshakeState : IHandshakeState
 17{
 18    private const byte HandshakeVersion = 0x00;
 419    private static readonly HandshakePattern s_handshakePattern = HandshakePattern.Xk;
 20
 21    private readonly SymmetricState _state;
 22    private readonly Role _role;
 23    private readonly Role _initiator;
 24    private readonly CryptoKeyPair _s;
 10425    private readonly Queue<MessagePattern> _messagePatterns = new();
 26
 27    private readonly IEcdh _dh;
 28    private CryptoKeyPair? _e;
 29    private byte[]? _re;
 30    private byte[] _rs;
 31    private bool _turnToWrite;
 32    private bool _disposed;
 33
 034    public CompactPubKey? RemoteStaticPublicKey => new(_rs);
 35
 36    /// <summary>
 37    /// Creates a new HandshakeState instance.
 38    /// </summary>
 39    /// <param name="initiator">If we are the initiator</param>
 40    /// <param name="s">Local Static Private Key</param>
 41    /// <param name="rs">Remote Static Public Key</param>
 42    /// <param name="dh">A specific DH Function</param>
 43    /// <exception cref="ArgumentException"></exception>
 10444    public HandshakeState(bool initiator, ReadOnlySpan<byte> s, ReadOnlySpan<byte> rs, IEcdh dh)
 45    {
 10446        if (s.IsEmpty)
 047            throw new ArgumentException("Local static private key required, but not provided.", nameof(s));
 48
 10449        if (s.Length != CryptoConstants.PrivkeyLen)
 050            throw new ArgumentException("Invalid local static private key.", nameof(s));
 51
 10452        if (rs.IsEmpty)
 053            throw new ArgumentException("Remote static public key required, but not provided.", nameof(rs));
 54
 10455        if (rs.Length != CryptoConstants.CompactPubkeyLen)
 056            throw new ArgumentException("Invalid remote static public key.", nameof(rs));
 57
 10458        ArgumentNullException.ThrowIfNull(dh, nameof(dh));
 59
 10460        _dh = dh;
 61
 10462        _state = new SymmetricState(ProtocolConstants.Name);
 10463        _state.MixHash(ProtocolConstants.Prologue);
 64
 10465        _role = initiator ? Role.Alice : Role.Bob;
 10466        _initiator = Role.Alice;
 10467        _turnToWrite = initiator;
 10468        _s = _dh.GenerateKeyPair(s);
 10469        _rs = rs.ToArray();
 70
 10471        ProcessPreMessages();
 10472        EnqueueMessages();
 10473    }
 74
 75    /// <inheritdoc/>
 76    /// <exception cref="ObjectDisposedException">Thrown if the current instance has already been disposed.</exception>
 77    /// <exception cref="InvalidOperationException">Thrown if the call to <see cref="ReadMessage"/> was expected or the 
 78    /// <exception cref="ArgumentException">Thrown if the output was greater than <see cref="ProtocolConstants.MaxMessag
 79    public (int, byte[]?, Encryption.Transport?) WriteMessage(ReadOnlySpan<byte> payload, Span<byte> messageBuffer)
 80    {
 9681        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(HandshakeState));
 82
 9683        if (_messagePatterns.Count == 0)
 084            throw new InvalidOperationException(
 085                "Cannot call WriteMessage after the handshake has already been completed.");
 86
 9687        var overhead = _messagePatterns.Peek().Overhead(CryptoConstants.CompactPubkeyLen, _state.HasKeys());
 9688        var ciphertextSize = payload.Length + overhead;
 89
 9690        if (ciphertextSize > ProtocolConstants.MaxMessageLength)
 091            throw new ArgumentException(
 092                $"Noise message must be less than or equal to {ProtocolConstants.MaxMessageLength} bytes in length.");
 93
 9694        if (ciphertextSize > messageBuffer.Length)
 095            throw new ArgumentException("Message buffer does not have enough space to hold the ciphertext.");
 96
 9697        if (!_turnToWrite)
 098            throw new InvalidOperationException("Unexpected call to WriteMessage (should be ReadMessage).");
 99
 96100        var next = _messagePatterns.Dequeue();
 96101        var messageBufferLength = messageBuffer.Length;
 102
 103        // write version to message buffer
 96104        messageBuffer[0] = HandshakeVersion;
 105
 576106        foreach (var token in next.Tokens)
 107        {
 108            switch (token)
 109            {
 160110                case Token.E: messageBuffer = WriteE(messageBuffer); break;
 32111                case Token.S: messageBuffer = WriteS(messageBuffer); break;
 72112                case Token.Ee: DhAndMixKey(_e, _re); break;
 88113                case Token.Es: ProcessEs(); break;
 32114                case Token.Se: ProcessSe(); break;
 0115                case Token.Ss: DhAndMixKey(_s, _rs); break;
 116            }
 117        }
 118
 96119        var bytesWritten = _state.EncryptAndHash(payload, messageBuffer);
 96120        var size = messageBufferLength - messageBuffer.Length + bytesWritten;
 121
 122        Debug.Assert(ciphertextSize == size);
 123
 96124        byte[]? handshakeHash = null;
 96125        Encryption.Transport? transport = null;
 126
 96127        if (_messagePatterns.Count == 0)
 16128            (handshakeHash, transport) = Split();
 129
 96130        _turnToWrite = false;
 96131        return (ciphertextSize, handshakeHash, transport);
 132    }
 133
 134    /// <inheritdoc/>
 135    /// <exception cref="ObjectDisposedException">Thrown if the current instance has already been disposed.</exception>
 136    /// <exception cref="InvalidOperationException">Thrown if the call to <see cref="WriteMessage"/> was expected or the
 137    /// <exception cref="ArgumentException">Thrown if the message was greater than <see cref="ProtocolConstants.MaxMessa
 138    /// <exception cref="System.Security.Cryptography.CryptographicException">Thrown if the decryption of the message ha
 139    public (int, byte[]?, Encryption.Transport?) ReadMessage(ReadOnlySpan<byte> message, Span<byte> payloadBuffer)
 140    {
 128141        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(HandshakeState));
 142
 128143        if (_messagePatterns.Count == 0)
 0144            throw new InvalidOperationException(
 0145                "Cannot call WriteMessage after the handshake has already been completed.");
 146
 128147        var overhead = _messagePatterns.Peek().Overhead(CryptoConstants.CompactPubkeyLen, _state.HasKeys());
 128148        var plaintextSize = message.Length - overhead;
 149
 128150        if (message.Length > ProtocolConstants.MaxMessageLength)
 0151            throw new ArgumentException(
 0152                $"Noise message must be less than or equal to {ProtocolConstants.MaxMessageLength} bytes in length.");
 153
 128154        if (message.Length != overhead)
 12155            throw new ArgumentException($"Noise message must be equal to {overhead} bytes in length.");
 156
 116157        if (plaintextSize > payloadBuffer.Length)
 0158            throw new ArgumentException("Payload buffer does not have enough space to hold the plaintext.");
 159
 116160        if (_turnToWrite)
 0161            throw new InvalidOperationException("Unexpected call to ReadMessage (should be WriteMessage).");
 162
 116163        var next = _messagePatterns.Dequeue();
 648164        foreach (var token in next.Tokens)
 165        {
 166            switch (token)
 167            {
 168168                case Token.E: message = ReadE(message); break;
 52169                case Token.S: message = ReadS(message); break;
 52170                case Token.Ee: DhAndMixKey(_e, _re); break;
 100171                case Token.Es: ProcessEs(); break;
 44172                case Token.Se: ProcessSe(); break;
 0173                case Token.Ss: DhAndMixKey(_s, _rs); break;
 174            }
 175        }
 176
 92177        var bytesRead = _state.DecryptAndHash(message, payloadBuffer);
 178        Debug.Assert(bytesRead == plaintextSize);
 179
 76180        byte[]? handshakeHash = null;
 76181        Encryption.Transport? transport = null;
 182
 76183        if (_messagePatterns.Count == 0)
 16184            (handshakeHash, transport) = Split();
 185
 76186        _turnToWrite = true;
 76187        return (plaintextSize, handshakeHash, transport);
 188    }
 189
 190    private void ProcessPreMessages()
 191    {
 208192        foreach (var token in s_handshakePattern.Initiator.Tokens)
 193        {
 0194            if (token == Token.S)
 195            {
 0196                _state.MixHash(_role == Role.Alice ? _s.CompactPubKey : _rs);
 197            }
 198        }
 199
 416200        foreach (var token in s_handshakePattern.Responder.Tokens)
 201        {
 104202            if (token == Token.S)
 203            {
 104204                _state.MixHash(_role == Role.Alice ? _rs : _s.CompactPubKey);
 205            }
 206        }
 104207    }
 208
 209    private void EnqueueMessages()
 210    {
 832211        foreach (var pattern in s_handshakePattern.Patterns)
 212        {
 312213            _messagePatterns.Enqueue(pattern);
 214        }
 104215    }
 216
 217    private Span<byte> WriteE(Span<byte> buffer)
 218    {
 219        Debug.Assert(_e == null);
 220
 80221        _e = _dh.GenerateKeyPair();
 222        // Start from position 1, since we need our version there
 80223        ((ReadOnlySpan<byte>)_e.Value.CompactPubKey).CopyTo(buffer[1..]);
 80224        _state.MixHash(_e.Value.CompactPubKey);
 225
 226        // Remember to add our version length to the resulting Span
 80227        return buffer[(CryptoConstants.CompactPubkeyLen + 1)..];
 228    }
 229
 230    private Span<byte> WriteS(Span<byte> buffer)
 231    {
 232        // Start from position 1, since we need our version there
 16233        var bytesWritten = _state.EncryptAndHash(_s.CompactPubKey, buffer[1..]);
 234
 235        // Don't forget to add our version length to the resulting Span
 16236        return buffer[(bytesWritten + 1)..];
 237    }
 238
 239    private ReadOnlySpan<byte> ReadE(ReadOnlySpan<byte> buffer)
 240    {
 241        Debug.Assert(_re == null);
 242
 243        // Check version
 88244        if (buffer[0] != HandshakeVersion)
 245        {
 8246            throw new InvalidOperationException("Invalid handshake version.");
 247        }
 248
 80249        buffer = buffer[1..];
 250
 251        // Skip the byte from the version and get all bytes from pubkey
 80252        _re = buffer[..CryptoConstants.CompactPubkeyLen].ToArray();
 80253        _state.MixHash(_re);
 254
 80255        return buffer[_re.Length..];
 256    }
 257
 258    private ReadOnlySpan<byte> ReadS(ReadOnlySpan<byte> message)
 259    {
 260        // Check version
 28261        if (message[0] != HandshakeVersion)
 262        {
 0263            throw new InvalidOperationException("Invalid handshake version.");
 264        }
 265
 28266        message = message[1..];
 267
 28268        var length = _state.HasKeys()
 28269                         ? CryptoConstants.CompactPubkeyLen + CryptoConstants.Chacha20Poly1305TagLen
 28270                         : CryptoConstants.CompactPubkeyLen;
 28271        var temp = message[..length];
 272
 28273        _rs = new byte[CryptoConstants.CompactPubkeyLen];
 28274        _state.DecryptAndHash(temp, _rs);
 275
 24276        return message[length..];
 277    }
 278
 279    private void ProcessEs()
 280    {
 96281        if (_role == Role.Alice)
 282        {
 44283            DhAndMixKey(_e, _rs);
 284        }
 285        else
 286        {
 52287            DhAndMixKey(_s, _re);
 288        }
 48289    }
 290
 291    private void ProcessSe()
 292    {
 40293        if (_role == Role.Alice)
 294        {
 16295            DhAndMixKey(_s, _re);
 296        }
 297        else
 298        {
 24299            DhAndMixKey(_e, _rs);
 300        }
 20301    }
 302
 303    private (byte[], Encryption.Transport) Split()
 304    {
 32305        var (c1, c2) = _state.Split();
 306
 32307        var handshakeHash = _state.GetHandshakeHash();
 32308        var transport = new Encryption.Transport(_role == _initiator, c1, c2);
 309
 32310        Clear();
 311
 32312        return (handshakeHash, transport);
 313    }
 314
 315    private void DhAndMixKey(CryptoKeyPair? keyPair, ReadOnlySpan<byte> publicKey)
 316    {
 317        Debug.Assert(keyPair != null);
 318        Debug.Assert(!publicKey.IsEmpty);
 319
 200320        Span<byte> sharedKey = stackalloc byte[CryptoConstants.PrivkeyLen];
 200321        _dh.SecP256K1Dh(keyPair.Value.PrivKey, publicKey, sharedKey);
 188322        _state.MixKey(sharedKey);
 188323    }
 324
 325    private void Clear()
 326    {
 136327        _state.Dispose();
 136328    }
 329
 330    public void Dispose()
 331    {
 104332        if (_disposed)
 333        {
 0334            return;
 335        }
 336
 104337        Clear();
 104338        GC.SuppressFinalize(this);
 339
 104340        _disposed = true;
 104341    }
 342
 343    ~HandshakeState()
 344    {
 0345        Dispose();
 0346    }
 347}